saropa_lints 14.3.0 copy "saropa_lints: ^14.3.0" to clipboard
saropa_lints: ^14.3.0 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.

Packagepub.dev/packages/saropa_lints

Releasesgithub.com/saropa/saropa_lints/releases

VS Code Marketplacemarketplace.visualstudio.com/items?itemName=saropa.saropa-lints

Open VSX Registryopen-vsx.org/extension/saropa/saropa-lints


14.3.0 #

Stops the analyzer plugin from driving the Dart analysis server to a multi-GB out-of-memory hang on large projects. Because the plugin runs inside the analysis server, running rules there forces the editor to hold the project's resolved model in memory. A real-memory safety valve now pauses rule execution before the server saturates RAM, previously-inert cache eviction bounds the plugin's own footprint, and a project that has enabled no rules at all defaults to the essential set in-editor. Rules you have explicitly enabled always run as configured. log

Changed #

  • The in-editor analyzer plugin defaults to the essential rule set only when a project has configured no rules at all, so an unconfigured editor session stays light. Rules you explicitly enable (via dart run saropa_lints:init, a diagnostics: entry, or a severity override) always run in-editor exactly as configured — the memory default never silently drops an opted-in rule. An explicit SAROPA_TIER (or saropa_tier / runtime_tier) still caps as before; full coverage runs anytime out-of-process with dart run saropa_lints scan.

Fixed #

  • The plugin now pauses rule execution when the analysis-server process crosses a memory cap and resumes when it recovers, a backstop against the server saturating RAM and hanging the editor. Set SAROPA_LINTS_MAX_RSS_MB to tune the cap (0 disables it).
  • The native plugin now bounds and evicts its internal caches under memory pressure instead of retaining them for the entire analysis-server session. No action required.

14.2.4 #

Fixes a false positive in the hardcoded-API-URL rule so it no longer flags endpoints you have already moved into a named configuration constant — the exact fix the rule asks for. log

Fixed #

  • avoid_hardcoded_api_urls no longer fires on a URL that is already the value of a const/static const field, a const collection entry, or an environment-config enum default; it now flags only inline URLs at call sites. No action required — any // ignore: you added on a config file can be removed.

14.2.3 #

This is a maintenance release with no changes to lint rules or analysis behavior. It fixes the release process so the VS Code extension reliably reaches the Marketplace alongside Open VSX, and slims the published extension by dropping development-only files that were never used at runtime, so the download is smaller. log

Maintenance
  • Trimmed the VS Code extension package from 1200 files (17.7 MB) to the runtime set by excluding dev-only fixtures and outputs from .vscodeignore: UX test screenshots (test-ux/), the i18n translation-audit reports (reports/), and source maps (**/*.map). The bare *.md rule only matched the package root, so nested reports/*.md had been shipping; it is now **/*.md with the README and CHANGELOG re-included. Packaging only. No action required.
  • Fixed the publish script silently skipping the VS Code Marketplace when VSCE_PAT was unset, even though vsce held a valid stored vsce login credential. The Marketplace step now falls back to the stored credential (verified read-only with vsce verify-pat) instead of skipping, so a logged-in machine publishes to the Marketplace as well as Open VSX. Publish tooling only. No action required.

14.2.2 #

This release adds an Essential lint rule that catches a common Flutter layout crash: animating a widget's size directly inside a wrapping or flowing layout, which throws a render error on every frame once the animation starts. The rule points you to the safe alternatives so the problem is caught in the editor instead of on a device. log

Added #

  • New rule avoid_animated_size_in_wrap (Essential) flags AnimatedSize placed directly inside a Wrap or Flow. That combination throws "RenderAnimatedSize was mutated in its own performLayout" every frame once the size animates, because Wrap/Flow lay each child out within their own measurement pass while AnimatedSize re-dirties itself. Move the AnimatedSize into a Column/ListView, or put a bounded box (SizedBox/ConstrainedBox) between the two.
Maintenance
  • Excluded the regenerated Dart build output from VS Code's file watcher in .vscode/settings.json. VS Code does not skip build/ by default, so its 1.14 GB of gitignored output was crawled on every open, adding watcher and index load. Editor config only. No action required.
  • Quieted the publish flow's extension locale audit on a clean pass. With every locale fully covered it printed all ~80 lines of the per-locale table and coverage matrix as info, burying the result; a passing audit now prints only the "fully translated" confirmation and the report path. Gaps and low-quality lines still surface as warnings on a failing audit. Publish tooling only. No action required.
  • Collapsed git's per-file "CRLF will be replaced by LF" warnings during the commit step into a single "Normalized N files (CRLF -> LF)" line. A locale regen touches dozens of JSON files, each emitting one such stderr warning; they are expected (core.autocrlf is set right after) so they are now counted rather than dumped, while any unexpected stderr still prints. Publish tooling only. No action required.
  • Made temp-dir cleanup in project_vibrancy_cli_test.dart tolerate the transient Windows file lock. On Windows the analyzer briefly keeps file handles open after a scan, so the teardown's immediate deleteSync intermittently failed with PathAccessException (errno 32) and flaked the suite; cleanup now retries briefly and ignores a residual lock. Test harness only. No action required.

14.2.1 #

This release introduces a dedicated "Upgrade Opportunities" dashboard to help you discover unused features in your dependencies and instantly generate contextual upgrade prompts for AI assistants. It also adds a new lint rule to prevent runtime SQL crashes with Drift acronym columns, alongside smarter hardcoded API URL detection. Finally, the extension interface is polished with correctly translated tooltips, collapsible changelog histories, and cleaner table layouts. log

Added #

  • New rule require_named_for_acronym_drift_columns (Professional) flags Drift column getters with an acronym that omit .named(). Drift's snake_case converter inserts an underscore before every uppercase letter, so contactSaropaUUID becomes the SQL column contact_saropa_u_u_i_d — not the contact_saropa_uuid a human predicts — and raw SQL written against the expected name crashes with "no such column" at runtime. The rule is report-only because pinning a column that already shipped renames it and needs a migration; add .named('snake_case') on new acronym columns to keep source and schema in sync. No action required.

Fixed #

  • avoid_hardcoded_api_urls now flags hardcoded URLs on an api. host, not just /api paths. The detection pattern previously required /api in the URL path, so the most common shape — an api. subdomain with an ordinary path such as https://api.example.com/users — slipped through and the rule missed its own documented bad example. URLs with neither an api. host nor an /api path still pass, so ordinary links are unaffected. No action required. log

Added (Extension) #

  • New "Upgrade Opportunities" dashboard — a focused view of the dependencies you have under-adopted. Separate from the dense Package Dashboard table, it lists only packages that have changelog features your code does not yet use, ranked by relevance, and for each shows the package, description, README logo, the unused features, the exact project files that import it (click to jump to the line), and a one-click "Copy upgrade prompt for AI". Open it from the Saropa Lints sidebar ("Upgrade Opportunities", shown once a scan finds any) or the command palette ("Open Upgrade Opportunities"). No action required.
  • Package Vibrancy detail pane adds a "Copy upgrade prompt for AI" button. It assembles a ready-to-paste prompt from the package's changelog — the new features classified as adoption candidates, plus your project's own call sites for that package — so you can hand an AI everything it needs to suggest where the new features fit, instead of pasting a raw changelog. The button appears whenever a package has adoptable features; open a package and click it to copy. No action required.
  • Adoption opportunities now surface for up-to-date packages, not just outdated ones. A caret constraint quietly carries a package across releases whose new features you may never have adopted — being on the latest version does not mean you use everything it offers. The scan now mines each package's full changelog history and cross-references it against the symbols your code actually uses, so a fully up-to-date package with unused capabilities still flags features worth reviewing. No action required.
  • Package Vibrancy table gains a sortable "Opportunities" column to find the needles across many packages. Each package shows the count of changelog features it offers that your code does not yet use (the unused feature names are in the cell tooltip); sort by the column to bring the most under-adopted packages to the top. The column hides itself when nothing is unadopted. No action required.
  • Toolbar adds "Copy opportunities for AI" to triage the whole project in one paste. It bundles the AI upgrade prompts of the highest-relevance under-adopted packages into a single clipboard copy, so one AI round can review the project instead of opening each package. The button appears only when at least one package has an adoptable feature. No action required.
  • The Package Dashboard sidebar row shows a count of packages with features worth adopting. The "Package Dashboard" entry in the Saropa Lints sidebar (Editor dashboards section) now reads "… · N to adopt" after a scan, so under-used dependencies are visible without opening the dashboard. The count refreshes with each scan and clears when nothing is unadopted. No action required.

Fixed (Extension) #

  • The "Unused features" tooltip now reads cleanly in 20 non-English locales. Machine translation had appended hallucinated text after the {features} placeholder (stray sentences and leaked _PH0_ sentinel fragments), so the tooltip displayed garbage in languages such as German, Spanish, Japanese, and Russian. Each locale's value was rewritten to the plain "

Changed (Extension) #

  • Package Vibrancy dashboard moves "copy as JSON" out of every table row and into the detail pane header. The per-row clipboard icon added a column to an already-dense table; the copy button now sits next to the detail pane's close button and copies whichever package is open in the pane. Open a package to copy its JSON. No action required.
  • The package detail panel's changelog now collapses each version, with only the latest expanded. A long upgrade gap could fill the panel with every intermediate release's notes; each version is now a fold-out, opened by default only for the newest release. Click any version to expand its notes. No action required.
Maintenance
  • Added a changelog opportunity miner (engine behind the "Copy upgrade prompt for AI" button) that classifies a package's full changelog history into adoption candidates ("a new feature you could use") using text heuristics only — no AI — extracts the API names each feature introduces, and cross-references them against the symbols the project actually uses to rank what is genuinely unadopted. The project source is walked once, shared between the import scan and the symbol-usage scan. Service layer with unit tests. No action required.
  • Added a rule-liveness report (dart run saropa_lints:accuracy_report) that scans the expect_lint fixtures and flags any rule declared in a fixture but never firing there — a gap the marker-text contract tests cannot catch. Report-only; not yet wired into CI. No action required.
  • Made every api_network fixture actually exercise its rule. The bad examples were stubs — top-level functions where the rule visits class methods, or missing the package import the rule gates on — so 20 of the network rules never fired on their own fixtures. Each bad example is now a realistic class method with the required import; all 34 api_network rules now trigger. Fixtures only; no rule behavior changed. No action required.
  • Started the same fixture-adequacy pass on code_quality rules: wrapped four bad examples the rule could never see (positional/named bool params and an unnecessary override need a class method; a duplicate-const example needed top-level declarations) so the rules now trigger. Fixtures only; no rule behavior changed. No action required.
  • Stopped the publish audit's duplicated-message check from flagging correctionMessages that enumerate parallel code examples. The inline-repeat heuristic, which exists to catch a prose paragraph pasted twice into one message, fired on two share_plus rules whose messages intentionally repeat an API/call fragment across before/after migrations; repeated windows that look like code (call syntax, method chains, casts, camelCase) are now exempt while prose duplication is still caught. Audit tooling only. No action required.

14.2.0 #

Consolidates four overlapping shrinkWrap: true rules down to one. A single scrollable could be flagged by up to four differently-named diagnostics, so a site suppressed under one rule name was re-flagged under another; the redundant three are now deprecated and avoid_shrink_wrap_expensive is the canonical rule covering the whole concern. log

Changed #

  • Deprecated three redundant shrinkWrap rules in favor of avoid_shrink_wrap_expensive. avoid_shrink_wrap_in_scroll, avoid_shrink_wrap_in_lists, and avoid_shrinkwrap_in_scrollview all policed the same shrinkWrap: true concern, so one site drew up to four diagnostics and an acknowledgment under one rule name did not suppress the others; the canonical avoid_shrink_wrap_expensive flags nested and non-nested cases alike while exempting the safe NeverScrollableScrollPhysics pattern. Deprecated rules are dropped from freshly generated tier configs — re-run init or write-config to clear them, or remove them from analysis_options.yaml by hand.

Fixed #

  • prefer_static_final_for_session_constant no longer flags ThemeCommonSize or ThemeCommonFontSize arithmetic. Those getters fold the avatar-scale preference and the system text scale, so hoisting them to a static final would freeze a value the user can change and show a stale UI; the rule now treats only ThemeCommonSpace as session-constant. No action required.
  • prefer_boolean_prefixes no longer flags boolean fields whose name is a serialization or schema contract. Fields on an Isar @collection/@embedded class, a Drift @DataClassName row, or carrying @JsonKey map their Dart name to a stored property, column, or wire key, so a rename would break persisted data or desync serialization; these are now exempt while ordinary private and state booleans still flag. No action required.

Changed (Extension) #

  • Code Health usage counts and the unused flag are now resolved per declaration, not matched by name. Previously every function sharing a name pooled into one count, so a heavily-used _dispose made every other _dispose look used and hid true orphans; usage is now attributed to the exact declaration each reference binds to, and runtime entry points (main, @pragma('vm:entry-point'), framework @override lifecycle hooks) are no longer mislabeled unused. No action required; scans fall back to the prior name-based count when a project cannot be resolved.
  • Manage Rule Packs treats a package's version variants as a pick-one choice. Packs targeting different majors of the same dependency (dio vs dio 5.x, Riverpod 2 vs 3, app_links vs app_links 6.x, and similar) now carry a "Pick one version" tag and are mutually exclusive — enabling one variant turns its siblings off, and rule_packs.enabled can never list two versions of the same package at once. No action required; the lockfile already gates rules to the version you ship.
Maintenance
  • Split the extension's 1709-line Package Vibrancy report builder into a thin composer plus four focused sibling modules (shared helpers, top chrome, package table, data payloads). Behavior-preserving — the rendered report is byte-identical. No action required.
  • Split the 1356-line command-catalog registry into a types module, three per-group entry data files, and a thin composer. Behavior-preserving — the composed catalog is identical (162 entries, same order). No action required.
  • Extracted the Issues tree's node types and its command layer (hide/suppress, copy, apply-fix) out of the 1340-line issuesTree.ts into sibling modules, leaving the tree-data provider in place. Behavior-preserving; the tree's tests pass unchanged. No action required.
  • Split the two largest dashboard stylesheets (dashboardChromeStyles.ts, violationsDashboardStyles.ts) into per-section sibling modules behind thin composers. The generated CSS is byte-identical. No action required.
  • Decomposed the remaining oversized webview view files into focused sibling modules: the report stylesheet, the command-catalog webview (its CSS and client script), the Project Vibrancy / Code Health controller (its client script), the Findings wide-report stats, and the Package Vibrancy report client script. All byte-identical or test-verified. No action required.

[14.2.0] #

Adds a performance rule that flags arithmetic in a widget's build() whose operands are all fixed for the app session — number literals, constants, and design-token size getters — so the value is computed once in a static final field instead of on every frame. The Package Dashboard now shows a live progress bar while a rescan runs, so a refresh no longer looks like the page has frozen behind a lone notification. The Rule Packs sidebar gains a wave of new concern packs so every rule now belongs to a selectable pack, including cross-cutting "lens" packs that group rules by task — memory leaks, UI polish, release readiness — rather than by category. log

Added #

  • New rule prefer_static_final_for_session_constant (Professional tier, info). Flags compound expressions in build() built only from session-constant operands (literals, const fields, and design-token getters such as ThemeCommonSpace.Footer.size) that recompute on every rebuild; hoist them to a static final field, which—unlike const—works because the token getters resolve at runtime. Bare single getters and anything depending on context, widget, parameters, or locals are not flagged.

Added (Extension) #

  • The Package Dashboard shows a live progress bar during a rescan. A rescan previously updated only a VS Code notification while the dashboard sat on stale data, so it read as hung; the dashboard now fills a determinate bar as each package is scanned and clears it when results refresh. No action required.
  • The package detail pane's Upgrade and Retry buttons now show a busy state. An upgrade runs pub get plus the full test suite (minutes) and a retry re-fetches over the network, but the buttons gave no in-pane signal; they now disable, show a spinner, and relabel ("Upgrading…" / "Retrying…") until the work finishes. No action required.
  • The Saropa Dashboards launchpad now carries the full Actions, Settings, and Help controls. A control band under the hero exposes run analysis, initialize config, the lint-integration / tier / run-after / UI-language settings (each showing its current value), and the help links, so the launchpad is a complete entry point rather than only a dashboard-of-dashboards; toggling a setting updates its label in place without restarting the scans. No action required.
  • Findings can now be grouped by Tier and by Pack(s). The Findings dashboard "Group by" dropdown and the Issues view group-by picker gain two dimensions: Tier (Essential → Pedantic) and Pack(s) (ecosystem, platform, and concern packs). Pack grouping is multi-key like OWASP — a rule belonging to several packs appears under each — and findings whose rule is in no pack collect under "No pack". Both resolve from bundled rule metadata, so they work on an existing report without re-running analysis.
  • Manage Rule Packs gains rule-finding aids. Searching now lists every matching rule in a "Matching rules" panel (each linking to its explanation and to its owning pack), shows a live "N packs · M rules" count beside the box, and highlights the matched text in rule names; section and domain headers read "12 packs · 340 rules" so you can see where rules concentrate before opening a group. No action required.
  • 16 new concern packs broaden Rule Packs coverage and overlap. Thirteen coverage packs give every previously-unpacked rule file a home — Widgets & build, Layout & scrolling, Animation & motion, Dialogs & overlays, Notifications, Naming & conventions, Class & constructor design, BuildContext safety, In-app purchase, Hardware & sensors, Freezed (codegen), File I/O & handles, and Project config & integrity — and three cross-cutting "lens" packs (Memory & resource leaks, UI polish & UX, Release readiness) deliberately span categories so the same rules can be opted into through a task-shaped lens. Packs are additive, so enabling several never double-flags a shared rule.

Changed (Extension) #

  • The sidebar Actions panel merged into Settings. The Actions and Settings panels sat directly adjacent and read as duplicates, so run-analysis and initialize-config now lead the Settings panel (the title-bar play button still runs analysis); the duplicate "Pick UI language" action was dropped because the Settings "UI language" row already shows the current language and changes it on click. No action required.
  • "Saropa Dashboards" is now a launchpad for all six dashboards. It opens instantly with the page chrome and live summary cards for Lints Config, Findings, Package, and Command Catalog (each with an "Open full screen" link), then streams Project Map and Code Health in as their scans finish instead of blocking on both. Each heavy pane has its own Rescan and an inline Retry when a scan fails. The "Saropa Dashboards" row now leads the sidebar's Editor dashboards list as its entry point. No action required.
  • Manage Rule Packs merges each pack's rule count and "View" link into one "N rules" link. The table previously carried a separate count column and a separate "View" button that did the same thing; clicking the "N rules" link now both shows the count and expands the pack's rule list. No action required.

Fixed (Extension) #

  • The consolidated dashboard no longer hangs on a blank "Scanning…" screen or renders corrupted CSS. Both scans ran behind one all-or-nothing gate, and Project Map's stylesheet was double-wrapped so its theme CSS spilled onto the page as visible text and the treemap rendered blank; panes now load independently and the stylesheet is injected verbatim. No action required.
  • The Manage Rule Packs coverage gauge now fills its arc instead of showing the percentage over an empty ring. The gauge's fill level was delivered through an inline style attribute the webview's content-security policy silently dropped, so the arc stayed empty; it is now set from the page script and animates up to the score. No action required.
  • Manage Rule Packs search now finds individual rules, not just pack names. Typing a rule name (or a problem area such as "storage") surfaces the pack that owns it and expands its rule list, where previously search matched only the pack's title. No action required.
  • Toggling several rule packs in quick succession no longer stacks multiple analyses. Each toggle re-ran analysis without stopping the previous run, leaving several "Running analysis" notifications and overlapping analyzer processes; a new run now cancels the in-flight one so only the latest runs. No action required.
Maintenance
  • Removed a redundant import from the pubspec constraint parser test that dart analyze --fatal-infos flagged (unnecessary_import), unblocking the publish analysis gate.

14.1.1 #

Fixes the rule detail panel, which displayed a wall of raw JavaScript instead of the rule's documentation, and removes its dead "View in ROADMAP" button. log

Changed (Extension) #

  • Removed the "View in ROADMAP" button from the rule detail panel. It linked to a redirect stub that no longer holds per-rule documentation, so the link led nowhere useful. No action required.

Fixed (Extension) #

  • Opening a rule in the detail panel no longer dumps raw script text into the view. A code comment in the panel's inline script contained a literal closing-script-tag sequence that terminated the script block early, so the browser parsed the rest as visible text. No action required.

14.1.0 #

Resolves a false positive in the color-only status indicator rule so decorative active-state bars and indicators paired with another visual cue are no longer flagged. The extension's "Create baseline" suggestion now actually creates the baseline. Package Vibrancy override analysis now recognizes overrides that resolve a transitive dependency cap, naming the package responsible instead of marking the override removable, and upgrade-blocker explanations trace the dependency path to a package you can act on. The "Annotate pubspec" command now writes why each pinned dependency sits where it does. The Findings Dashboard sheds a stray analysis banner and a redundant Run analysis button, the TODO/HACK file-limit note gains a one-click way to raise the limit, and Drift Advisor can now reach a server running on another device over your network. Package Vibrancy also stops re-scanning on every restart — unchanged projects load instantly from cache and refresh quietly in the background. log

Added #

  • New rule require_android_exact_alarm_permission flags exact-alarm scheduling when the manifest declares neither SCHEDULE_EXACT_ALARM nor USE_EXACT_ALARM. Android 14 (API 34) denies the exact-alarm capability by default, so a zonedSchedule(..., androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle) (or AndroidAlarmManager.oneShotAt(..., exact: true)) is silently downgraded to inexact and fires late or never, with no error anywhere. Inexact schedule modes are not flagged. Essential tier; declare the permission or switch to an inexact mode.
  • New rule require_android_partial_media_permission suggests READ_MEDIA_VISUAL_USER_SELECTED when broad media permissions are declared. When the manifest already declares READ_MEDIA_IMAGES/READ_MEDIA_VIDEO and the app uses a gallery plugin, omitting the partial-access permission hides Android 14's "Select photos" option and forces all-or-nothing library access. Advisory (Professional tier); it fires only once the broad permissions are present, so it stays quiet for Photo Picker apps.
  • New rule avoid_package_js_for_wasm flags package:js imports, which break flutter build web --wasm. package:js has no WebAssembly implementation, so its presence fails the Wasm build; the supported replacement is the built-in dart:js_interop. Distinct from the existing dart:js/dart:html rules, which do not cover the third-party package:js. Comprehensive tier (Wasm is opt-in).
  • New rule avoid_platform_incompatible_dependency warns when you import a plugin with no implementation for a platform your project builds for. A Flutter plugin compiles into every target even when its native side is missing, so importing, for example, sqflite (no web) into a project with a web/ directory builds cleanly and then throws at runtime on web only — a failure the compiler never flags. The rule fires only when the project has the matching platform directory and the import is unconditional, and is backed by a hand-verified package list (sqflite, local_auth, path_provider, firebase_messaging, camera, permission_handler). Opt in via the Comprehensive tier; guard the import conditionally or switch packages to resolve.
  • Platform rule packs — iOS, Android, Web, Windows, macOS, and Linux — group each target's rules so you can enable a platform's checks without raising your whole tier. Each is recommended automatically when its embedder folder (ios/, web/, …) is present, and enabling a pack only adds rules on top of your tier. Open Manage Rule Packs and toggle the platforms you ship.
  • Concern rule packs — Security, Performance, Accessibility, Networking, Error handling, and nine more — group existing rules by what they protect rather than by package. They surface a whole problem area (for example every security rule) that would otherwise stay buried behind the Professional tier, and a rule may belong to several packs at once. Opt in from Manage Rule Packs under the Concerns section.

Added (Extension) #

  • A "Create Baseline" command that suppresses your existing violations so only new code is flagged. The Suggestions view already prompted you to baseline once findings existed, but the prompt opened the config editor and left you to run the baseline tool from a terminal yourself. The prompt — and a new command-palette entry, "Saropa Lints: Create Baseline" — now run it directly, with a cancellable progress notification, and refresh the views when it finishes. No action required.
  • The "Annotate pubspec" command now writes why each pinned dependency sits where it does, so you no longer paste pub's conflict output by hand. Above each affected dependency it adds a comment explaining whether the version is held back by a shared dependency (naming the path to a package you can edit), forced up to a required minimum by another package, or incompatible with a Flutter SDK pin. Re-running the command refreshes these notes in place and leaves your own hand-written comments untouched. Run "Saropa Lints: Annotate pubspec" after a scan to populate them.
  • Drift Advisor can now connect to servers running off-box, via a new saropaLints.driftAdvisor.hosts setting. Discovery previously scanned localhost only, so a Drift server running on a phone reached over Wi-Fi debugging was never found. List one or more LAN IPs (for example 192.168.1.151) to probe in order; each is scanned across the port range, or pin an exact endpoint with host:port. Defaults to ["127.0.0.1"], so no action is required unless a server is remote.
  • Pack recommendations now read your project's folders and config files, not just pubspec dependencies. The Config dashboard suggests platform packs from the embedder folders you build for (ios/, web/, …) and Firebase from a google-services.json / GoogleService-Info.plist, so a pack is offered even when the signal is a folder or file rather than a dependency line. Review and enable from the startup suggestion or Manage Rule Packs.

Fixed (Extension) #

  • Package Vibrancy no longer mislabels a dependency_overrides entry as removable when it exists to resolve a transitive version cap. Override analysis only checked your direct constraint and an SDK-pin heuristic, so an override pinning a dependency that a sibling package caps below the resolved version showed as "stale," inviting you to delete an override the resolver actually needs. It now reads each constrainer's declared range and names the binding package as the override's reason. No action required.
  • The Findings Dashboard no longer shows a stray "Analysis started" banner above the header or a second blue "Run analysis" button in the empty state. The accessibility announcer rendered visibly because this view's stylesheet omitted the rule that hides it, and the no-data panel duplicated the toolbar's Run analysis and Refresh; the announcer is hidden again and the empty panel now relies on the toolbar action. No action required.

Changed (Extension) #

  • Upgrade-blocker explanations now trace the dependency path to a package you can edit. When the package capping a shared dependency was buried deep in the transitive graph, the report named a constrainer absent from your pubspec.yaml, leaving you with no actionable line. The explanation now appends the chain from a direct dependency down to that constrainer (for example, build_runner → build → analyzer_plugin). No action required.
  • The TODO/HACK scan "file limit reached" note is now actionable instead of a dead end. It previously named the setting key in prose with no way to act on it; it now offers a "Raise the limit" button that opens the relevant setting directly. No action required.
  • Package Vibrancy no longer re-runs the full scan on every restart — when pubspec.lock and your settings are unchanged, cached results load instantly with no progress popup. The startup gate previously expired after an hour, forcing a 10-minute network scan even though nothing had changed; it now reuses the cache as long as the lockfile is identical, and only refreshes silently in the background once the data passes a staleness window (backgroundRefreshStalenessHours, default 24h; set 0 to disable). No action required.
  • Cold scans are faster on large projects: package analysis now runs 6 in parallel (was 3) and is tunable via saropaLints.packageVibrancy.scanConcurrency. Raise it above the default only with a GitHub token configured, to avoid hitting unauthenticated rate limits. No action required.

Changed #

  • Rule packs are now additive: enabling a pack adds its rules on top of your tier instead of replacing tier coverage. Previously any rule that belonged to a pack was switched off unless that pack was enabled, so a tier could silently lose rules it should have kept; now a rule is active when your tier or an enabled pack includes it. Version-specific migration rules remain the exception — a rule for a package or SDK major you are not on (for example a dio 5 rule on a dio 4 project) stays off even if your tier lists it. Expect more rules to fire on projects that use packaged dependencies; no action required.

Fixed #

  • avoid_color_only_indicators no longer fires when the state is already conveyed by a non-color cue or when the color is just a show/hide toggle. The rule inspected a Container in isolation, so a thin active-tab underline whose color toggles to Colors.transparent (a presence cue, not a red-vs-green hue) and a colored swatch sitting next to a sibling Text/Icon that reacts to the same state (bold weight or distinct glyph) were both reported as inaccessible. The rule now suppresses both: a branch resolving to Colors.transparent is treated as a visibility toggle, and a sibling Text/Icon-family widget referencing the same condition counts as the required secondary cue — which also fixes the rule's own documented GOOD example (a Row with a conditional Icon beside the colored Container). Genuine standalone red/green indicators with no secondary cue still warn. No action required.
Maintenance
  • When dart pub get / dart pub deps fails on a version conflict, the scan log now captures pub's own "Because … is forbidden / is required" reasoning verbatim, instead of only a generic "version solving failed". Diagnostic only.
  • The new concern and platform rule packs now carry pubspec markers, restoring the registry invariant that every pack has a marker entry. The rule-pack generator emits the concern-pack markers so they stay in sync. Fixes a unit-test failure that blocked the build. No user-facing change.
  • Publish runs the unit-test step roughly twice as fast: it now uses all logical cores and excludes the five slow-tagged full-repo integration tests (scanner / cross-file / health-history), which CI still runs in full on every push and release. Run them locally with dart test -t slow. Developer tooling only.

14.0.7 #

A quick fix for better honoring // ignore. log

Fixed #

  • A // ignore: written below a /// doc comment and directly above the declaration is now honored on declarations the rule flags as a whole. The conventional placement (doc comment, then // ignore:, then the static/final/type line) previously did nothing for rules that report the whole declaration — e.g. avoid_global_state, require_http_status_check, require_file_close_in_finally — forcing the directive above the doc comment or onto a trailing line. The suppression check keyed off the doc-comment line instead of the declaration line; it now matches the declaration line, consistent with how the same placement already worked for nested diagnostics. No action required; existing above-doc and trailing placements still work.
Maintenance
  • Hardened the analyzer constraint guard in pubspec.yaml. The analyzer: ^12.1.0 cap now carries a boxed HARD STOP note documenting that analyzer 13 renamed the public AST classes the package is built on (NamedExpressionNamedArgument, SimpleFormalParameterRegularFormalParameter, DefaultFormalParameter removed) with no deprecated aliases — a bump is a ~912-reference source migration across ~148 files, on top of the existing meta ^1.18.3 Flutter-stable resolution blocker. No published constraint or behavior change.

14.0.5 #

This release resolves two false positive scenarios to make your linting experience smoother. The keyboard dismissal rule now correctly ignores scrollable areas that do not actually contain editable text fields. We have also exempted specific Flutter render-object overrides from parameter mutation warnings, recognizing that the framework inherently requires in-place modification for these methods. log

Fixed #

  • require_keyboard_dismiss_on_scroll no longer fires on scroll views that contain no text field. The rule now warns only when a ListView / GridView / CustomScrollView / SingleChildScrollView actually contains an editable field (TextField, TextFormField, CupertinoTextField, or a custom *TextField), matching its own "containing text fields" precondition; a pure content list (contacts, avatars, a continent list) has no keyboard to dismiss and is now left alone. Builder/opaque content with no syntactically visible children is intentionally not flagged. No action required.
  • avoid_parameter_mutation no longer fires inside Flutter render-object overrides where mutating the framework-supplied object is mandatory. updateRenderObject(BuildContext, RenderObject) (pushing the widget's new config onto the render object) and setupParentData(RenderObject child) (assigning child.parentData) are now exempt — there is no copy alternative, so the in-place mutation is the framework contract, not caller-data corruption. Same render-object blind spot previously fixed for avoid_unassigned_late_fields and avoid_unsafe_cast.

14.0.4 #

This release introduces four thematic rule packs that bundle quality standards for UI, localization, documentation, and testing into simple opt-in groups. The standalone scanner now supports full type resolution to accurately evaluate complex rules, and it will no longer abort an entire run if a single check fails. Additionally, this update delivers significant accuracy improvements by eliminating false positives across recursion, initialization, and platform-specific code. log

Added #

  • Four thematic rule packs group cross-cutting quality rules into one-click bundles. ui_excellence (keyboard ergonomics, visible async feedback, image layout stability, formatted numbers, predictable dialogs/lists), localization (externalize strings, locale-aware intl formatting, plurals/RTL), documentation (public-API dartdoc completeness), and testing (deterministic, isolated, well-structured tests). Enable them in the VS Code Manage Rule Packs dashboard under the new Quality standards domain, or add the id under rule_packs.enabled in analysis_options.yaml. Until now every pack was tied to a package or SDK version; these are the first packs that name a quality standard.
  • scan --resolve runs the standalone scanner with full type resolution. Rules that fire on constructor calls like File('x') (or that need resolved types) only see violations under resolution, because the default syntactic pass parses an implicit constructor as a method call and never triggers them. The flag is slower and needs the target project's pub get; the default fast path is unchanged.

Changed #

  • The rules in the new ui_excellence / localization / documentation / testing packs are now pack-owned, so they are opt-in like every other rule pack. A pack-owned rule fires only when its pack is enabled (in the dashboard or rule_packs.enabled), even though it remains catalogued in a tier. These rules were previously enabled implicitly by tier selection; to keep them on, enable the matching pack(s). An explicit false in diagnostics: still wins over pack opt-in.

Fixed #

  • The scan CLI no longer silently under-reports instance-creation and type-based rules. The default scan is syntactic, where an implicit constructor (File('x')) is not yet an instance-creation node, so every rule on that channel quietly never fired; run scan --resolve (or check via the IDE/custom_lint plugin, which is always resolved) to evaluate those rules. The default fast pass still covers all other rules and is unchanged.
  • A misbehaving rule can no longer abort the whole scan run. A rule that throws while visiting one file is now reported by name and skipped for that file, instead of crashing the scanner and losing every result; the remaining files and rules still run. No action required.
  • avoid_recursive_calls no longer fires on recursion that has a terminating base case. It now suppresses the warning when a guard clause returns or throws before recursing, when a ternary's other branch terminates, or when every self-call is bounded by a loop over a collection (tree/JSON walks, divide-and-conquer) — instead of flagging every direct self-call. No action required.
  • avoid_unassigned_fields no longer fires on a field initialized by a required this.field (or optional this.field / super.field) named parameter. Named and optional parameters wrap the initializing formal in a DefaultFormalParameter, which the constructor scan did not unwrap, so the common const-data-class pattern (const C({required this.x})) was wrongly reported as unassigned. Only positional this.field parameters were recognized before; now named, optional-positional, and super formals are all treated as assigning the field.
  • avoid_throw_objects_without_tostring no longer fires on throw Error.throwWithStackTrace(obj, stack) when obj's class has a useful toString(). That call always returns Never, so the rule was checking Never instead of the actually-thrown object and flagging every such throw. It now inspects the first argument's type, and recognizes a toString() override inherited from any non-Object superclass (not only one declared directly on the thrown class). Genuine violations — a thrown class with no toString() — are still reported, including when thrown via throwWithStackTrace.
  • prefer_final_fields no longer suggests final for a field that is reassigned from another class, and is now limited to private fields. It previously counted only this-qualified writes inside the declaring class, so a field mutated through an instance held elsewhere (entry.count++, ctx.flag = x) was wrongly reported as never reassigned — and applying the fix failed to compile. Writes are now matched by resolved element across the whole file, and only private (_-prefixed) fields are flagged, because a write to a public field in another library cannot be seen by single-file analysis. Use prefer_final_fields_always to flag every non-final field.
  • move_variable_outside_iteration no longer suggests hoisting a loop variable whose value changes each iteration. It now suppresses the warning when the declaration's initializer reads a local that is reassigned elsewhere in the loop (an ancestor walk's dir = dir.parent, a for counter, a j++), since hoisting would freeze it at the first iteration's value and break the loop. Genuinely invariant declarations are still flagged. No action required.
  • require_platform_check no longer fires on dart:io usage inside the native branch of a conditional import or export (the *_io.dart file selected by if (dart.library.io)). Such files never load on web — the web build resolves to a separate stub — so the file split itself is the platform guard and no kIsWeb check is needed. The rule now applies the same conditional-import suppression the sibling prefer_platform_io_conditional rule already used; the underlying scanner additionally recognizes conditional export directives (not only import) and the *_io.dart/*_stub.dart naming pair. A file that is also reached by an unconditional import can still load on web, so it is still flagged. No action required.
  • require_permission_status_check no longer fires on methods that merely share a gated name (startRecording, startScan, getContacts, and similar) when the receiver is an unrelated app-domain type. It now requires the call target to resolve to a recognized media/permission source (camera, geolocator, image picker, scanner, speech, audio recorder, contacts, permission) before flagging, instead of matching the bare method name — so a plain in-process recorder or scanner is no longer reported. Genuine camera, location, or microphone calls without a permission check still are. No action required.
  • A leading // ignore: now suppresses a diagnostic reported on a node nested inside a multi-line statement. When a rule flagged an expression that sat on a later line than the statement it belonged to — for example avoid_recursive_calls pointing at the inner self-call of a multi-line return — an // ignore: written directly above the statement was silently ignored, because the suppression check compared the directive against the flagged expression's line rather than the statement's. The directive above the statement now works, matching how // ignore: behaves for single-line statements. No action required.
Maintenance
  • The reports/organize_reports.py helper now runs standalone instead of requiring the contacts repo cloned alongside. The shared move/prune organizer was imported only from ../contacts/scripts/.shared/, so the script aborted on any checkout without the sibling repo. A vendored copy now lives at scripts/.shared/reports_organizer.py and the launcher loads it first, falling back to the contacts copy only when the vendored module is absent. Dev-only.

14.0.3 #

A new avoid_cascade_shuffle rule catches a subtle bug where (collection..shuffle()).first permanently reorders a shared list just to read one element. Five new pubspec rules review your version-constraint hygiene — flagging an open-ended SDK bound, dependencies pinned to any, and (for applications) ranges so wide the team drifts onto different versions. Turning off Lint integration now actually stops the analyzer. Previously "Lint integration: Off" only flipped an internal flag, so saropa_lints diagnostics kept appearing in the Problems pane. log

Added #

  • New avoid_cascade_shuffle rule (Recommended tier). Flags (collection..shuffle()).first and similar, where ..shuffle() is cascaded onto a stored list whose result is consumed, because shuffle() mutates in place and corrupts the shared collection for every other reader; shuffle a copy instead — (List.of(collection)..shuffle()).first.
  • Five new pubspec version-constraint rules. require_sdk_upper_bound (Recommended) flags an SDK constraint with no upper bound, which lets pub get resolve against an untested future SDK major. avoid_unbounded_dependency (Recommended) flags dependencies pinned to any. require_dependency_lower_bound (Professional) flags constraints with only an upper bound. For applications only (publish_to: none), prefer_caret_constraint_in_app (Professional) suggests ^1.2.3 over the equivalent >=1.2.3 <2.0.0, and avoid_overly_wide_app_constraint (Comprehensive) flags ranges spanning two or more majors. The app-only rules stay silent for published packages, which legitimately need wide ranges.

Fixed (Extension) #

  • "Lint integration: Off" now comments out the plugins: block in analysis_options.yaml so the analyzer stops emitting saropa_lints diagnostics; toggling it back On restores the block with your rule packs and overrides intact. No action required.
  • Drift Advisor anomalies and index suggestions no longer appear twice in the Problems panel when the standalone Saropa Drift Advisor extension is also installed; the Lints integration now defers the Problems publish to that extension while it is active, and resumes if you disable it. No action required.
  • When a Drift Advisor server connects and the standalone Saropa Drift Advisor extension is not installed, a one-time per-workspace toast now recommends installing it for the full Problems-panel experience; it honors the existing proactive-nudge opt-out. No action required.
  • The "Drift Advisor" product name is now shielded from machine translation so it stays in English across every locale instead of being transliterated; affected catalogs correct themselves the next time locales are regenerated. No action required.
Maintenance
  • The locale audit now treats strings that are entirely brand terms, {placeholders}, and punctuation (e.g. Saropa Lints: {message}) as skipped rather than missing, since machine translation can only echo them; this clears two perpetual false-positive coverage gaps.

14.0.2 #

This release introduces a unified multi-pane dashboard that lets you review your project map and code health metrics side by side, alongside an on-demand shortcut to quickly re-check for package updates. It also addresses key interface stability issues, preventing extension host freezes during upgrades and stopping the primary dashboard header from flickering during active analysis. log

Fixed (Extension) #

  • The Findings Dashboard header no longer flickers constantly. The dashboard reloads itself whenever the analyzer republishes diagnostics, and each reload replayed the header's entrance animation — so in an actively-analyzing project the header strobed nonstop. It now skips the reload when nothing you can see has changed, and only animates the header on first open. No action required.
  • The "Upgrading Saropa Lints to X" notification no longer hangs open after you accept an upgrade. The upgrade ran a full project analysis on the blocking call path, which froze the extension host for the whole analysis and left the progress notification (and its Cancel button) unresponsive until VS Code was reloaded. Analysis now runs without blocking and is cancellable, so the notification closes when the upgrade finishes. No action required.

Added (Extension) #

  • New "Saropa Lints: Open Saropa Dashboards" command shows the Project Map and Code Health dashboards side by side on one page. Each pane keeps its full interactive content — the treemap, churn-complexity scatter, and hot-spot table, beside the score status line, KPI filters, and sortable function table — so you can compare where size and complexity concentrate against which functions score worst without switching tabs. Clicking a row opens the file; "Open full screen" on either pane reopens the standalone dashboard. The two standalone commands are unchanged. No action required.
  • Click the "Scanned X ago" pill on the Package Dashboard to rescan and re-check for package updates. The pill is now a button: clicking it refreshes the dashboard and re-runs the pub.dev version check, re-surfacing the "Update available" notification even after you dismissed it. The same action is available from the command palette as "Saropa Lints: Check for Package Updates Now". No action required.

14.0.1 #

The Project Map dashboard now hides machine-generated and localization files from its size map and hot-spot rankings, so the files it surfaces are ones you can actually improve. Previously a single generated database file or a megabyte of translation tables would dominate the list and bury the real issues. log

Fixed #

  • The Project Map dashboard no longer ranks generated and localization files in its size map and hot spots. Files emitted by code generators (.g.dart, freezed, drift, auto_route, injectable, protobuf, and similar) and the app_localizations* / intl_* translation tables now stay out of the rankings, matching the Code Health dashboard's existing behavior. These files are long, mechanical, and unimprovable, so they crowded out the hand-written code that hot spots are meant to highlight. No action required.

Changed #

  • The warnings for require_keyboard_visibility_dispose, avoid_openai_key_in_code, and require_speech_stop_on_dispose now spell out the failure each prevents. The three messages were far shorter than the rest of the catalog and stopped at the symptom; they now describe the leaked widget, the billable key abuse, and the held microphone in full. No action required.

  • The Saropa dashboards now share one consistent visual style, and the Project Map follows your editor theme. The Project Map dashboard previously rendered a fixed palette that ignored your light / dark / high-contrast theme; it now tracks the active theme in the editor (while standalone HTML reports keep their styled palette). The Code Health scanning screen, the About panel, the rule-violations dashboard, the command catalog, and the package-details sidebar were all brought onto the shared design system, so colors, spacing, and type match across every surface. No action required.

Maintenance
  • Removed three orphaned localization keys left by the Suggestions sidebar removal. configSuggestions.packAvailable, configSuggestions.initMissing, and configSuggestions.badgeTooltip were only ever read by the deleted Suggestions tree provider; no code referenced them after that view was removed. Dev-only.
  • Generated-file detection is now one shared predicate, used by every CLI. The list of code-generator suffixes plus gen-l10n table detection lived inline in several scanners; it is now a single isGeneratedDartPath helper the analysis CLIs share, so extending the list updates every consumer at once. The cross-file analyzers (unused symbols, duplicates, unused l10n, missing mirror tests), project_vibrancy, and the Project Map size scanner all delegate to it; the predicate also recognizes a generated path segment and the full codegen-suffix set, so each consumer now agrees on what counts as generated. Dev-only.
  • Added a cross-project dashboard style guide. A canonical design-system document (docs/design/SAROPA_DASHBOARD_STYLE_GUIDE.md) defines one token set, component contract, and accessibility gate for every Saropa dashboard surface, so the extension's dashboards stop diverging into separate visual styles. Docs-only.
  • The shared dashboard chrome now carries the full token scale, and the six non-conforming surfaces adopt it. dashboardChromeStyles.ts gained the spacing / radius / type / elevation / motion / z-index tokens (plus an exported getDashboardTokens() for surfaces that keep bespoke components), and the About panel, Code Health scan screen, Project Map, rule-violations dashboard, command catalog, and package-details sidebar were re-tokened onto it — bespoke layouts (the score gauge, command tiles, package badges, scan stepper) kept, only their values converged. Dev-only.
  • Clarified the dashboard style guide's scope after first adoption. Added an explicit exemption for high-density log/terminal consoles, a note that VS Code collapses the four-step surface ramp onto two host backgrounds, and brought Saropa Log Capture's dashboard webview panels into the per-platform adoption section. Docs-only.
  • Adopted the style guide's button, badge, and grade-color hardening rules. Secondary buttons in the shared chrome now carry a fallback fill and a guaranteed border, so host themes that leave --vscode-button-secondaryBackground undefined no longer render buttons as bare text; and letter grades across the Code Health report and the scan screen now drive off one shared A–F ramp derived from the semantic tokens instead of per-surface grade colors. Dev-only.
  • Reconciled the style guide body with its VS Code reference implementation — the surface-0 caveat, 13px type base, and standalone --brand-glow value now match chromeTokens() instead of disagreeing with it. Docs-only.
  • Began a consolidated "Saropa Dashboards" view that shows Project Map and Code Health on one page. A new saropaLints.openDashboards command opens a host webview that embeds each dashboard's full interactive report in its own iframe, side by side, preserving every chart and interaction (no summarizing). The Project Map pane plus the iframe drill-down message bridge are in place; the Code Health pane follows once the iframe mechanism is confirmed in the Extension Development Host. The standalone Project Map and Code Health commands are unchanged. Dev-only until complete.
  • Added instantiation-pin tests for six package rule packs that had none (Envied, Keyboard Visibility, Google Fonts, OpenAI, Speech to Text, uuid), fixing the tier-integrity check that requires every rule category to carry a test and closing the gap that let a sub-standard message ship unnoticed. Dev-only.
  • A release-commit push to main no longer triggers a redundant ci run. The publish workflow already validates that exact tagged commit and the publish script mirrors the full gate locally, so cutting a release stops firing three overlapping workflows at once. Dev-only.
  • Closed the stub-test tracking plan by dropping its deferred follow-up scope. The plan's stub removal and the hard zero-gate that keeps empty-body test/testWidgets stubs out are complete and verified; the never-started "rewrite removed stubs as fixture-backed tests" backlog was removed as not needed. Docs-only.

14.0.0 #

Hardens the release pipeline so a missing dependency can no longer reach pub.dev, and teaches the Package Vibrancy dashboard to name the real package behind a stuck upgrade when two dependencies fight over a shared transitive dependency. Also lays the foundation for the Saropa suite: the extension now exports its findings to a shared file the Drift Advisor and Log Capture extensions can read, and contributes stable deep-link commands so those tools can jump into a rule. Replaces the catch-all "Mixed packages" rule pack with one focused pack per package (OpenAI, uuid, Envied, Google Fonts, and others) so you can enable just the rules for packages you actually use, and reworks the rule-packs dashboard — renamed Manage Rule Packs — with an A–Z table, an inline rule list, a one-click "Enable all recommended packs" action, and the noisy Suggestions sidebar removed. The only action required is for projects that enabled the old package_specific pack (see Changed). Polishes the extension dashboards too, so their filters can be driven from the keyboard and their colors follow your editor theme under light and high-contrast modes. log

Added #

  • Six focused rule packs replace the catch-all "Mixed packages" bucket: OpenAI, uuid, Envied, Google Fonts, Keyboard Visibility, and Speech to Text. Each gates on its own dependency, so enabling one no longer pulls in rules for packages you do not use; the rules themselves are unchanged. If you enabled package_specific, see Changed for the migration.

Changed #

  • The package_specific ("Mixed packages") rule pack was removed and its rules split into per-package packs. The 19 unrelated rules it bundled now live in dedicated packs gated by the package each targets (existing packs like app_links, image_picker, webview_flutter, url_launcher, geolocator, google_sign_in, firebase, plus the six new packs above). If your analysis_options.yaml lists package_specific under rule_packs.enabled, replace it with the specific packs you want — the old id is now ignored, so those rules turn off until you do.

Fixed #

  • A standalone // ignore: placed on its own line directly above a ternary ?/: branch is now honored by the headless scanner. Previously only a trailing same-line ignore suppressed a diagnostic reported on a ternary operand, so a correctly-placed leading ignore was silently dropped; add the ignore above the branch as expected and it now applies. No action required.

  • avoid_bloc_event_in_constructor no longer flags list.add(...) or controller.add(...) in a Bloc constructor. It flagged every method named add, so populating a local list or stream controller during construction was wrongly reported as dispatching a Bloc event; it now flags only an unqualified add(event) on the Bloc itself. No action required.

  • avoid_ref_in_dispose and avoid_ref_inside_state_dispose no longer flag an unrelated field or local named ref used in dispose(). They reported any ref by name, so a non-Riverpod object named ref was wrongly flagged; they now report only when ref resolves to Riverpod's WidgetRef/Ref. No action required.

  • avoid_nullable_async_value_pattern no longer flags .value on a variable merely named with "async". It matched the variable name, so any asyncThing.value on an unrelated type was wrongly reported; it now flags only .value on a real AsyncValue. No action required.

  • require_immutable_bloc_state no longer flags a ...State-named class that is not a Bloc state. Any concrete class whose name ended in State (such as a RequestState value object) was flagged purely by name; it now fires only when the class is used as a Bloc/Cubit state type argument. No action required.

  • prefer_cubit_for_simple now counts event handlers from the code, not from comments or strings. The handler count came from a text scan that matched on<...> tokens inside comments and string literals and missed nested generics like on<Wrapper<int>>; it now counts real on<Event>(...) registrations, fixing both over- and under-counting. No action required.

  • avoid_logging_sensitive_data no longer flags logging an OAuth flow identifier such as oauthToken. The intended OAuth carve-out never worked and the rule treated the token inside oauthToken as a leaked secret; it now suppresses only when the sensitive word is part of a known safe identifier. No action required.

  • prefer_streaming_response no longer flags .body or in-memory uses of .bodyBytes. It also fired on the String .body and on any .bodyBytes that merely shared a scope with an unrelated file variable; it now flags only .bodyBytes written to disk in the same function. No action required.

  • avoid_over_fetching no longer flags ordinary repository methods that fetch and return a single field. A short method like final r = await api.fetch(id); return r.id; was reported with no real over-fetch signal; it now fires only when a fetched object's single field feeds another call (such as a widget). No action required.

  • require_token_refresh no longer double-flags an auth class and no longer fires when refresh logic is present. A class that stored an access token alongside a refresh token and refresh method was still flagged for lacking an inline expiry check; the rule now reports once, only when no refresh signal exists at all. No action required.

  • avoid_jwt_decode_client no longer flags constructing a JWT model object outside an authorization check. Building a value like JsonWebToken.fromMap(...) for display or storage was wrongly reported; the rule now requires the same role/permission decision context as its method-call form. No action required.

  • require_biometric_fallback now catches the canonical localAuth.authenticate(biometricOnly: true) call. The receiver-name match missed single-token receivers like localAuth, so biometric-only logins went unflagged; it now matches the local_auth biometric API. No action required.

  • avoid_webview_javascript_enabled no longer fires when JavaScript is disabled but an unrelated flag (e.g. isInspectable: true) is set. The rule now reads the actual boolean value of javaScriptEnabled instead of scanning the argument text for true, so InAppWebViewSettings(javaScriptEnabled: false, isInspectable: true) stays silent. No action required.

  • prefer_cached_getter no longer flags repeated plain field reads. It treated any property accessed twice in a method (widget.title, a struct field) as an expensive getter to cache, even though a plain field read is a direct memory fetch. It now fires only when the repeated access resolves to an explicitly-declared get accessor; a field's implicit (synthetic) getter is skipped. No action required.

  • Ten widget-pattern rules stopped firing on false matches by checking resolved types and code structure instead of names and text. avoid_stateful_widget_in_list now flags only StatefulWidget list items, avoid_duplicate_widget_keys treats Key('x') and ValueKey('x') as distinct, avoid_gesture_conflict requires direct nesting, prefer_tap_region_for_dismiss no longer trips on populate/closest, prefer_asset_image_for_local matches only assets/ paths, avoid_nullable_widget_methods no longer flags non-widget types named "…Widget", avoid_double_tap_submit no longer treats a "Reorder" button as submit, avoid_late_without_guarantee detects a real initState assignment, avoid_static_route_config matches exact router types, and prefer_split_widget_const counts only const-constructible children. Fewer spurious warnings; no action required.

  • avoid_string_concatenation_loop no longer flags numeric += accumulators. A loop body like total += count or resultMap[k] += n was reported as O(n²) string concatenation based only on the variable name containing result/output/buffer/message. The += branch now requires the accumulator to actually be a String (by resolved type), so numeric accumulation stays silent while genuine String += ... still reports. No action required.

  • Several widget-lifecycle rules now check resolved AST structure and element identity instead of text/lexeme matching. require_super_dispose_call, require_super_init_state_call, and avoid_set_state_in_dispose were silently dead — they tested a method's direct parent for ClassDeclaration, but that parent is the class body node, so they never fired; they now resolve the enclosing State subclass and report again. avoid_inherited_widget_in_initstate and avoid_expensive_did_change_dependencies no longer fire on a same-named initState/didChangeDependencies method declared on a plain (non-State) class. avoid_unsafe_setstate no longer treats setState() in the else branch of if (mounted) as guarded. prefer_widget_state_mixin no longer counts unrelated members like _unfocusTimer/_focusableItems as interaction-state fields (whole-word, bool-typed match). avoid_scaffold_messenger_after_await walks the body in source order so a ScaffoldMessenger.of(context) call lexically before the await is not reported as "after". require_field_dispose detects disposal via real cascade sections, so a sibling field disposed inside another field's cascade argument no longer satisfies the wrong field. require_scroll_controller_dispose skips controllers read from widget.* (parent-owned). avoid_recursive_widget_calls matches self-instantiation by resolved element, so a same-named imported widget is no longer flagged. require_init_state_idempotent matches removeListener/removeObserver as a real invocation instead of a source substring. No action required.

  • Seven widget-layout rules now use resolved AST checks instead of source-text or name matching. avoid_opacity_misuse flags only a state-toggled ternary, so a static const such as opacity: _kDisabledOpacity stays silent; prefer_fractional_sizing matches a real MediaQuery…size.width|height read by structure, so a same-named local like fakeMediaQuery.size.width * 0.5 no longer trips; prefer_page_storage_key recognizes a PageStorageKey by type and is no longer fooled by lookalike names; avoid_stack_without_positioned skips children of a Stack that sets its own alignment/fit (the intentional badge-over-avatar pattern); prefer_spacing_over_sizedbox treats SizedBox(height: 8) and 8.0 as the same spacer; avoid_builder_index_out_of_bounds compares full receiver chains so a guard on other.items.length no longer excuses items[index], catching a real out-of-bounds risk; and avoid_hardcoded_layout_values drops a dead duplicate branch with no behavior change. No action required.

  • require_stream_error_handling no longer flags a non-Stream object whose name ends in "controller". A call like animationController.listen(...) was reported for a missing onError because the name-based fallback treated any "…controller" receiver as a stream. It now requires the resolved Stream type, an explicit .stream access, or a name ending in "stream"; a bare AnimationController/ScrollController stays silent. No action required.

  • prefer_utc_for_storage no longer flags a toIso8601String() used for a UI label when an unrelated storage call is in the same method. The context scan ran storage patterns against every enclosing node's full source, so any save(/insert(/.set…( elsewhere in scope marked a display-only timestamp as "storage". The scan now stops at the immediate enclosing statement, so only the statement that actually persists the value is considered. No action required.

  • handle_throwing_invocations no longer flags an unrelated dart:io call such as systemEncoding.decode(...). A catch-all final clause matched any dart:io element whose method name appeared in the throwing-call set, so non-I/O members named decode/parse were wrongly reported; detection is now restricted to the documented throwing calls qualified by both library and method (dart:io read*/write* sync I/O, dart:convert decode/jsonDecode, dart:core parse). No action required.

  • avoid_dialog_context_after_async no longer misjudges a mounted check by position when comments or whitespace precede the pop. The mounted-check scan sliced a re-rendered source string at original-source offsets, so a guard placed after Navigator.pop(context) could be credited as guarding it (and a real one before it missed). It now compares AST node offsets directly, so a pop guarded only by a later mounted check is correctly flagged. No action required.

  • prefer_no_commented_out_code no longer flags a wrapped prose sentence that cites a function call. A multi-line comment such as "…Without this, formatNumberLocale(x, decimalPlaces: 25) crashed (formatDouble in…" was flagged on the middle line because it names a call, even though it is mid-sentence English; the rule now treats a lowercase continuation line carrying English function words or a dangling parenthesis as prose, while genuine commented-out code under a prose lead-in still reports. The same change fixes comment lines containing prefix, suffix, fixture, or toStringAsFixed being misread as FIX task markers (marker words now match only as whole words), which also affects prefer_capitalized_comment_start. No action required.

  • prefer_setup_teardown no longer flags subject construction that is parameterized per test across a group. When three tests build the subject with the same literal (AsyncSemaphoreUtils(1)) while sibling tests in the same group build it with different literals ((2), (3)), the construction is per-test arrange, not a hoistable fixture — extracting one variant into setUp() would leave the others constructing locally. The rule now collapses call shapes that differ only by a literal argument and suppresses when more than one variant exists in the group, while genuinely identical setup still reports. No action required.

  • require_route_guards no longer fires on routes whose path merely contains a protected word. This ERROR-level rule treated /reorder as the protected segment order and /accounting as account (substring match), demanding an auth guard on unprotected routes. It now matches whole /-split path segments, so only a route segment actually named e.g. account or admin is flagged. No action required.

  • avoid_go_router_push_replacement_confusion no longer flags non-detail routes by substring. context.go('/viewport/$x') matched the detail segment /view and was reported; matching is now per path segment, so /viewport no longer trips the view rule. No action required.

  • avoid_push_replacement_misuse no longer fires on builder bodies and route objects. It lowercased the whole argument source, so MaterialPageRoute(builder: () => ListView()) matched view and an Order… builder matched order. It now inspects only the route name (the pushReplacementNamed string and RouteSettings(name:)) by segment. No action required.

  • require_deep_link_testing no longer treats uuid:/valid: as an id. The rule checked whether the route argument source contained id:, so an object built with a uuid: or valid: argument was wrongly considered to carry a routable id and skipped. It now inspects argument labels and map keys for an exact id, so objects lacking one are correctly flagged. No action required.

  • avoid_pop_without_result no longer confuses similarly-named variables. A result from await Navigator.push(...) was considered "used without a null check" when an unrelated resultValue appeared nearby, and its null guards were missed for the same reason; it now tracks the variable by element identity and recognizes == null / != null / ! / ?. handling. No action required.

  • prefer_layout_builder_for_constraints no longer flags two legitimate MediaQuery sizing patterns. It fired on a MediaQuery.sizeOf read that is the fallback branch of a constraint-finiteness guard (constraints.maxWidth.isFinite ? constraints.maxWidth : MediaQuery.sizeOf(context).width) — code that already uses LayoutBuilder and only consults MediaQuery when the parent passes unbounded constraints, where there is no LayoutBuilder value to use. It also flagged a screen-fraction height scaled by a named factor (MediaQuery.sizeOf(context).height * fraction) inside an unbounded scroller, where LayoutBuilder would yield infinite constraints; the screen-fraction exemption previously required a numeric literal and now accepts any *// factor. Breakpoint comparisons still require a numeric literal, so genuine width: MediaQuery.sizeOf(context).width sizing keeps flagging.

  • Line-level // ignore: no longer over-suppresses rules whose name is a prefix of another. An // ignore: my_rule_extended comment was matched by a bare substring check, so it also silenced the distinct, shorter my_rule on the same line or declaration — hiding real diagnostics. Matching is now whole-word (\b-anchored), consistent with // ignore_for_file:. If you relied on a single ignore accidentally covering a prefix-named rule, add that rule to the comment explicitly.

  • Baseline files no longer suppress violations in the wrong file when two filenames share a suffix. A baseline entry for util.dart was matched against my_util.dart (and any path merely ending in the same characters) because path comparison used an unbounded suffix check, hiding real violations in unrelated files. Suffix matching now requires a path-segment (/) boundary, so relative-vs-absolute paths still match but distinct files do not.

  • avoid_future_tostring no longer fires on FutureOr values, and the Future-handling rules now recognize Future subtypes. Several async rules (avoid_future_ignore, avoid_future_tostring, prefer_return_await, avoid_unawaited_future) decided "is this a Future?" by checking whether the type's display name starts with Future — which wrongly flagged FutureOr<T> (a false positive on .toString()/interpolation) and missed types that implement Future. They now use a proper type check (Future or a Future subtype, excluding FutureOr), removing the false positives and catching previously-missed Future subtypes.

  • avoid_expensive_build no longer flags cheap built-in conversions in build(). int.parse, double.tryParse, DateTime.parse, and Uri.parse were matched purely by the method name parse/tryParse, so these common, inexpensive calls were reported as expensive build-time work. The rule now skips parse/tryParse whose resolved result is a core primitive while still flagging heavy parsing such as jsonDecode.

  • incorrect_firebase_event_name no longer fires on non-Firebase analytics. This ERROR-level rule flagged any logEvent(name: ...) call against Firebase's naming rules, but Mixpanel, Segment, and Amplitude expose the same method with different rules. It is now gated to files that reference Firebase.

  • require_database_index and require_database_migration no longer fire in projects with no embedded database. Their query/model heuristics matched ordinary in-memory collection access (.users, .items, .where(...)) and unrelated model classes; both are now gated to files that reference Isar/Realm/ObjectBox (index) or Hive/Isar (migration).

  • avoid_drift_unsafe_web_storage no longer fires without a Drift import. It flagged any WebDatabase(...) constructor or any method named unsafeIndexedDb, even in code that does not use Drift; it now requires a drift/drift_flutter import like every other Drift rule.

  • avoid_throw_in_catch_block now runs everywhere, not only in BLoC files. This general reliability rule (throwing in a catch block without preserving the stack trace) was restricted to files containing a BLoC/Cubit, so it was effectively inactive for most code despite being a Recommended-tier rule. It now applies to all Dart files; if this surfaces more findings than wanted, use rethrow / Error.throwWithStackTrace or disable the rule.

  • Android permission checks no longer match a longer permission that shares a prefix. A check for READ_CONTACTS also matched a manifest declaring only READ_CONTACTS_EXTENDED, so manifest-based rules could mis-detect a granted permission. Matching is now boundary-anchored to the exact permission name.

  • Android manifest edits are now picked up without restarting the analyzer. The manifest was cached per project for the whole analysis-server session with no invalidation, so adding a permission or service didn't change results until restart. The cache now refreshes when the manifest's size or modification time changes (same approach already used for iOS Info.plist).

  • iOS background-mode detection no longer misreads an unrelated audio/location string. hasIosBackgroundAudioConfigured/...LocationConfigured checked for the mode string anywhere in Info.plist, so a plist enabling only background location that mentioned audio elsewhere reported audio as enabled. The mode is now matched inside the UIBackgroundModes array.

Added (Extension) #

  • Package Vibrancy now flags ~40 more abandoned packages and points each at its maintained successor. New cross-grades cover Community Edition databases (isar/isar_flutter_libsisar_community/isar_community_flutter_libs, hive_flutterhive_ce_flutter), the last Flutter Community Plus gap (wifi_info_flutternetwork_info_plus), discontinued packages pub.dev itself names a replacement for (the AngularDart packages → ng*, uni_linksapp_links, artemisferry, super_enumfreezed, and more), and abandoned widgets/plugins (flutter_web_authflutter_web_auth_2, pdf_renderpdfrx, nfc_in_flutternfc_manager, others). Most offer a one-click "Replace with…" pubspec fix; no action required.

  • A production crash can now point you at the lint rule that would have prevented it. When the Saropa Log Capture extension records a runtime crash (a "No element", a null-check-operator failure, a RangeError, and others) and the Saropa Lints rule that catches that class is currently disabled, a one-time prompt offers to enable that rule. The prompt appears at most once per rule and only when Log Capture's diagnostics file is present, so it never nags. No action required if you do not use Log Capture.

  • The Config dashboard now has a "Style & opinions" section for the opt-in stylistic rules. The ~220 stylistic rules — off in every tier, previously only reachable through the setup wizard — are grouped by concept (naming, formatting, ordering, widget style, and more) in a collapsed accordion below the packs. Conflicting choices (single vs double quotes, early-return vs single-exit, and the rest) render as pick-one radios so two contradictory rules can never be enabled at once; the rest are independent toggles with enable-all / disable-all per group. Each group carries a one-line description — including a noisiness warning where it matters (Ordering, Naming, Formatting) — so you can tell a quiet rule from a reformat-everything one before turning it on. Turning a rule on writes a rule: true override; turning it off removes the override. Off by default — nothing changes until you opt in.

  • The packs list is split into "For your project" and "All packages", and the full catalog is grouped by domain. Packs whose dependency or SDK matches your pubspec open by default in a short, relevant list; the full catalog sits in a collapsed accordion below, sub-grouped into collapsible domains (State management, Networking & APIs, Storage & persistence, Navigation & deep links, Media & graphics, Device & platform, Identity & sharing, Utilities & config, and SDK migrations) so you can browse by problem area instead of one ~80-row list. Each domain shows a one-line description, and detected packs carry a domain chip so their area is visible at a glance. Search and column sorting span every group, and a search auto-opens the groups that contain a match; no action required.

  • Static findings now export to a shared file the Saropa Drift Advisor and Log Capture extensions can read. After analysis settles, the extension writes its current findings to .saropa/diagnostics/lints.json (the Saropa Diagnostic Envelope) so the sibling tools can correlate your live database and runtime behavior against the rules that govern them; no action required, and nothing leaves your machine.

  • Sibling tools can deep-link straight to a rule, enable it, or open a finding. The extension contributes stable saropaLints.explainRule, saropaLints.enableRule, and saropaLints.openFinding commands so a Drift Advisor or Log Capture suggestion can jump into Rule Explain, turn on the covering rule, or open the exact source line; no action required.

  • The dashboard now badges a rule when a sibling tool confirms it at runtime. When Drift Advisor or Log Capture has written its mirror and points a runtime issue at one of your rules, that rule's row gains an "Advisor confirms at runtime" or "Log Capture saw N" badge, so you can see which static findings are backed by live evidence; appears only when a sibling extension is installed and active.

  • A Drift finding can jump straight to its live runtime issues in Drift Advisor. The editor lightbulb on a Drift-rule finding now offers "Show live Drift issues (Drift Advisor)" when the Drift Advisor extension is installed, so you can confirm a static finding against the running database in one click; the action is hidden when that extension is absent.

  • Projects paired with Drift Advisor are pointed at the third suite tool, once. A project that dev-depends on saropa_drift_advisor but does not have the Log Capture extension shows a single suggestion to install it (so runtime SQL can be correlated with static findings); it is gated per workspace and respects the proactive-nudge opt-out, so it never nags.

  • Exported findings are stamped with the current commit, so the suite tools can line up per commit. Each diagnostic written to the shared file now carries the workspace's commit SHA, letting Drift Advisor and Log Capture correlate "at this commit, the code had these findings" against their own runtime data; resolved by reading .git (no git process is spawned) and omitted outside a git checkout.

  • Package Vibrancy now explains shared-dependency (diamond) upgrade blocks, including SDK pins. When a package is held back because a sibling caps a shared transitive dependency it needs — dart_style stuck because another dependency pins analyzer low, or a package pinned by the Flutter SDK's exact characters/collection/meta — the dashboard names the blocker, the shared dependency, and the resolvable-vs-latest gap instead of showing an unexplained block; no action required.

  • constrained packages now name the constraint holding them back. A package the resolver could lift but your own pubspec caps now shows "your constraint ^x caps this — y resolvable" so the line to edit is obvious, instead of a bare "constrained" label; no action required.

  • Git, path, and SDK dependencies no longer show as stuck pub upgrades. A version gap on an overridden or SDK dependency is annotated as managed (and its "update available" squiggle suppressed), because such deps can't be bumped by editing a constraint; no action required.

  • Deliberately-pinned dependencies are marked as intentional holds. A "do not bump" / "do not use" note in a dependency's pubspec comment is read and shown as a pin, and its upgrade nag suppressed, so a frozen dependency reads as a decision rather than neglect; no action required.

  • Cross-project version drift against sibling repos. Set saropaLints.packageVibrancy.siblingRepoPaths to other repo folders and a package pinned at a lower major than a sibling (e.g. saropa_lints ^9.7.0 here vs ^13.12.7 elsewhere) is flagged as behind, surfacing a lagging consumer pub-outdated can't see; off until paths are configured.

  • The Package Vibrancy dependency graph now zooms, pans, and opens collapsed to direct dependencies. It starts showing only your direct dependencies (so a large project's graph is readable on open) with an "Expand transitives" button to reveal the full depth-2 view; drag to pan, use the zoom buttons or mouse wheel to zoom, "Reset view" to refit, and clicking a node centers it before jumping to its table row. No action required.

  • The package screen now shows a consolidated changelog of every release between your version and the latest. Release notes are scraped (the package's CHANGELOG first, pub.dev as fallback) and rendered inline so you can read what actually changed before adopting a newer version, rather than upgrading blind; no action required.

Changed (Extension) #

  • The "new versions available" notification no longer offers a one-click "Update All". Bulk-pulling every newest version from an unsolicited toast, without reviewing each changelog, is a supply-chain risk (a freshly-published malicious release would be adopted across your whole dependency graph in one tap); the toast is now awareness-only ("View Details" / "Dismiss") and you upgrade per package from the package screen, where the new consolidated changelog makes each upgrade a reviewed decision. No action required.
  • The Package Vibrancy dashboard now shows a package's full detail in a docked side pane. Selecting a row opens its versions, community metrics, alerts, platforms, and links in a master-detail pane beside the table — the rich detail that previously required a separate editor tab — so you can scan the list and read detail in one place; the pane is hidden until you select a row, and closes with its × or Escape. No action required.
  • The Lints Config dashboard is now "Manage Rule Packs" and opens sorted A–Z by pack name. The clearer name surfaces the powerful, easily-missed package packs, and name-sorting makes the long pack list scannable instead of ordering by an opaque rule count; no action required.
  • The pack table leads with a "Recommended" column and every header now has an explanatory tooltip. The old mid-table "In pubspec" column moved to the front and was renamed, so the packs that apply to your project (their dependency or SDK gate is satisfied) are the first thing you see; no action required.
  • A pack's rules now expand inline instead of opening a popup, and each rule is a link to its explanation. Clicking "View" reveals the rule list in a row beneath the pack and each rule opens Rule Explain, so you can inspect a pack without losing the table; no action required.
  • New "Enable all recommended packs" button turns on every pack your project's dependencies and SDK satisfy in one click. It enables package packs whose dependency is present, SDK packs your environment satisfies, and applicable version-upgrade packs, after a confirmation; no action required.
  • The pack-type filter dropdown is now readable in dark themes. Its options bound to your editor's dropdown theme colors instead of rendering low-contrast gray-on-black; no action required.

Removed (Extension) #

  • The standalone "Suggestions" sidebar panel was removed. Its long "Enable the X rule pack" list was noise; applicable packs are now surfaced by the single startup notification (whose action opens Manage Rule Packs) and the dashboard's "Enable all recommended packs" button. No action required.

Fixed (Extension) #

  • Package Vibrancy no longer mislabels finished first-party packages (e.g. path_provider) as merely "outdated". A mature flutter.dev/dart.dev/google.dev/firebase.google.com package that publishes rarely because it is complete could score into the "outdated" band on low repository churn; trusted publishers are now lifted one band to "stable" (matching the existing "stable" → "vibrant" promotion), while genuinely dead packages are untouched. No action required.

  • The Package Vibrancy dependency graph now follows the table's filters. It rendered once from the full dependency set and ignored the search box, age slider, dev-dependencies toggle, presets, and chart filters, so it showed packages you had filtered out; it now re-renders to match the visible rows, dropping hidden packages and any edge to one. No action required.

  • A short package-update poll interval can no longer run away. Setting saropaLints.packageVibrancy.watchIntervalHours to 0 (or a negative/invalid value) created a zero-millisecond timer that polled the pub registry continuously; the interval is now floored at 15 minutes and falls back to 6 hours for invalid values.

  • Translations that are intentionally empty are now respected. The localization lookup treated an empty-string translation as "missing" and fell back to English; it now distinguishes a missing key from an intentionally blank value.

  • Findings dashboard sections now open compact again. Every group except the first re-collapses on load as intended, instead of all sections expanding, so a large report opens scannable rather than as one long wall; no action required.

  • Dashboard filters are now fully keyboard-accessible. The Package Vibrancy summary cards focus with Tab and toggle their filter on Enter or Space, and a visible focus ring was added to the package-comparison Add buttons, the rule-triage actions, and the project-vibrancy score-threshold input, so the dashboards can be driven without a mouse; no action required.

  • Several dashboard surfaces now follow light and high-contrast themes correctly. Package-status badge text, the Findings dashboard top-rules text, and a few control borders and focus rings were bound to editor theme colors instead of fixed values that could wash out or disappear in the opposite theme; no action required.

  • Command Catalog category headers stay visible while scrolling. The sticky section label now pins just beneath the toolbar instead of sliding behind it, so you can always see which category you are scrolling through; no action required.

  • Dashboards no longer overflow sideways in a narrow editor pane. The Package Vibrancy, Known Issues, and package-comparison tables now scroll horizontally within their own bounds and the package-detail link strip wraps, so a docked or split-narrow webview shows the content instead of a whole-page horizontal scrollbar; no action required.

  • Dashboards are now navigable by screen reader. Each editor dashboard exposes a single banner and one main landmark (a duplicate nested <header> is gone), wraps its summary and controls in labeled regions, gives the icon-only table columns accessible names, corrects a skipped heading level in the empty state, underlines comparison links so they are not distinguished by color alone, and styles the Findings skip link so it stays hidden until focused; no action required.

  • Dashboard text now meets WCAG AA contrast in every theme. Status pills, category labels, muted card labels, the "Unused" badge, and the footprint/filter toggles were lifted off colors that dipped under the contrast minimum on light, dark, or high-contrast themes — the semantic hues are preserved (and the large KPI numbers, grade badges, and charts keep their full color) so the dashboards read clearly without losing their look; no action required.

Maintenance
  • The publish analyze step no longer loops forever on the package's own mid-publish plugin-version error. When saropa_lints dogfoods itself, dart analyze reports the plugin dependency as a constraint (^14.0.0) while pubspec.yaml holds the bare version (14.0.0); the mid-publish guard compared the two with string equality, so the caret made it fall through to the interactive [F]/[S] "fix stale cache" prompt — which can never resolve because the package's own analysis_options.yaml has no plugin version: pin to edit, so it cleared the cache, retried, and re-prompted indefinitely. The guard now strips the leading constraint operator before comparing, so the benign mid-publish state is recognized and analyze is treated as passed. Dev-only.
  • The empty-body stub-test guard no longer fails on a deliberately skipped placeholder. A test(..., () {}, skip: '...') documents an un-runnable case (e.g. a Flutter-gated rule the Flutter-less example package cannot exercise) and never executes, so its empty body cannot silently pass — yet the hard zero gate counted it and turned the guard, CI, and the publish audit red. The scanner now excludes any test/testWidgets carrying a skip: argument; a genuine () {} stub with no skip: is still rejected. Dev-only.
  • Regression test pins the Package Vibrancy published-age math against device_calendar 4.3.3. The whole-month age helper is now exported and accepts an injectable reference date, so a fixed-now test asserts the calendar-month, day-of-month-rollback, UTC-neutrality, and clamp behaviors that the old days/365 calculation got wrong. No production behavior change.
  • New publish audit gate: every package imported by shipped code must be a declared dependency. The release audit (STEP 1, before any tag is pushed) now scans lib/ and bin/ for real import/export directives and fails if an imported package is absent from pubspec dependencies. This catches the class of defect that sank v13.12.6 and v13.12.7 (a meta import with no meta dependency): lib/** is in analyzer.exclude for plugin dogfooding, so no dart analyze run inspects these imports, and dart pub publish only rejected them on the post-tag CI job — after the tag was burned. The gate is deterministic and Dart-version-independent, and ignores package: URIs that appear inside rule detection patterns or DartDoc examples.
  • The same gate now runs in CI on every push and pull request. A new scripts/check_dependency_imports.py (shared logic with the release audit) runs in the analyze job, so a missing dependency fails at merge time rather than at release. The pre-existing dart pub publish --dry-run CI step could not be relied on for this: its exit code for a missing dependency is Dart-version-dependent and was treated as a non-fatal warning.
  • New Playwright UX render harness for the editor dashboards. npm run ux renders each dashboard builder's HTML against a light/dark/high-contrast --vscode-* theme shim, runs axe-core, checks horizontal overflow at narrow and wide widths, and captures screenshots — so visual and accessibility regressions are caught outside the VS Code host. Dev-only; generated pages and screenshots are gitignored.
  • Removed the retired inline package-detail card builders from the dashboard source. After single-package detail moved into the docked pane, buildDetailCard and its file/vulnerability/dependency/links section builders were no longer called; deleting them drops dead weight from the webview bundle. The shared Health Score section builder it used was kept (the pane now renders it). No behavior change.
  • Publish no longer crashes after the tag is pushed when the release workflow runs long. The gh run watch step had a hard 5-minute timeout with no handler, so a longer publish workflow raised an uncaught timeout after the version tag was already created. It now waits up to 10 minutes (aligned with run discovery) and, on timeout, prints the Actions monitor URL instead of crashing.
  • The dependency-import publish gate no longer false-blocks on a commented dependencies: header. A dependencies: # comment line was not recognized, so the parser saw zero declared dependencies and reported every shipped import as undeclared. The header now tolerates a trailing comment.
  • get_latest_changelog_version only matches real headings. The version lookup was unanchored and could match a version token in prose or a code fence; it now requires a line-leading ## heading, consistent with the changelog display path.
  • The [Unreleased] publish matchers now only match real headings too. has_unreleased_section and rename_unreleased_to_version used an unanchored regex, so a release note that quotes ## [Unreleased] inside a backtick code-span was read as a leftover Unreleased section after the heading had already been renamed — making the publish step raise "both [Unreleased] and [version] exist" and silently bump to the next patch. Both now anchor to a line-leading heading. Dev-only.
  • The publish prompt now offers a pre-written CHANGELOG release version instead of the stale pubspec patch. The default version was derived only from pubspec.yaml plus whether an ## [Unreleased] section exists, so hand-writing a release section that consumed the [Unreleased] heading (e.g. a ## [14.0.0] major bump) left the prompt suggesting the old pubspec value. It now takes the higher of the pubspec-derived default and the latest ## [X.Y.Z] heading. Dev-only.
  • Project Health size scan stays memory-flat on large projects and ignores a UTF-8 BOM. The NDJSON writer is now flushed with backpressure as the scan streams rows (its buffer no longer grows with file count), and a leading byte-order mark is stripped before line counting so a BOM-prefixed first line is not miscounted as code.
  • New resolved-analyzer rule-test harness. test/support/resolved_rule_harness.dart runs a single lint rule against inline source with full type/element resolution and returns the diagnostics it reports, so a rule's detection can be asserted to fire on a violation and stay silent on compliant code. Previously the rule tests only pinned metadata and counted expect_lint strings, so detection false positives/negatives shipped unverified. Dev-only; used by the new async/parse/gating regression tests.
  • The British-English spelling guard now only scans files inside the repo. The PostToolUse hook fired on edits to files outside the repository (e.g. a global editor config), flagging unrelated content — including reference tables that legitimately list British spellings — as violations. It now skips any path that does not resolve under the repo root; the git pre-commit path and all in-repo edits are unaffected. Dev-only.
  • Every extension command/notification message is now localizable. All showInformationMessage / showWarningMessage / showErrorMessage calls (and their action-button labels) across the extension were routed through l10n() with {token} interpolation instead of hardcoded English or string concatenation — 208 keys under a new notify.* namespace in en.json. English output is unchanged; the translated locale catalogs are filled by the separate translation pass. Quick-pick / input-box / progress titles and webview HTML are not part of this pass.
  • Closed the last three extension translation gaps by hand. Filipino Cross-project drift, Indonesian info, and the Russian rule-pack-review error toast had come back as untranslated English from the MT pass; each now has a curated dictionary entry and locale-catalog value, bringing every locale to full coverage.
  • Translation generator auto-corrects when launched on a free-threaded Python. generate_translations.py relaunches under the standard python.exe beside a free-threaded build (via a blocking subprocess, so the console isn't shared and the interactive menu's stdin stays intact), because the shared NLLB runtime (numpy/ctranslate2) is compiled for the standard ABI and fails to import under python3.14t; if no standard build is found it errors with the exact command instead of a deep numpy ImportError. Dev-only.
  • Interactive translation menu previews the gap state first and reads cleaner. An interactive launch of generate_locales.py now runs the read-only audit before the mode menu so the choice is informed by what is actually missing, adds blank lines around the menu (the block was crushed against the preceding output), and states the Enter default in the prompt (default 1). Dev-only.
  • Ctrl-C on the translation generator now stops promptly instead of finishing the whole in-flight locale. The cooperative-cancel flag was honored by the prefetch loop but not by the per-leaf mapping pass, so after a stop request the generator silently made live NLLB/Google calls for every remaining string in the current locale before exiting; the shared single-string path now also short-circuits on cancel, serving already-cached work and leaving the rest as a gap the next run resumes. Dev-only.
  • Closed the last two extension translation gaps by hand. The German dependency-sort detail ({detail} in {sections}) and the Persian command-notification brand prefix (Saropa Lints: {message}) came back identical to English from the MT pass — German "in" is spelled the same and the Persian string is brand-plus-placeholder with no translatable words — so each now has a curated dictionary passthrough, bringing every active locale back to full coverage. Dev-only.
  • Closed the Italian and Hindi translation gaps by hand. The Italian dependency-sort detail ({detail} in {sections}) and the Hindi command-notification brand prefix (Saropa Lints: {message}) returned identical to English from the MT pass — Italian "in" is spelled the same and the Hindi string is brand-plus-placeholder with no translatable words — so each now has a curated dictionary passthrough, keeping the coverage gate honest. Dev-only.
  • Bumped the extension build's esbuild devDependency to 0.28.1 to clear GHSA-gv7w-rqvm-qjhr. The advisory's RCE only affects esbuild's Deno install path via a malicious NPM_CONFIG_REGISTRY; the extension bundles through the Node path (which already verifies binary hashes) and esbuild never ships to users, so there was no runtime exposure. Dev-only.
  • Resolved the shell-quote critical advisory (GHSA-w7jw-789q-3m8p) in the extension's dev dependency tree. A transitive dev-only dependency was bumped via npm audit fix; it is build/test tooling and never ships to users. Two remaining low-severity diff-via-mocha advisories have no non-prerelease fix and stay until mocha 12 is stable. Dev-only.

Historical Changelog Archive #

Looking for older changes? See CHANGELOG_ARCHIVE.md for version 13.13.0 and older.

7
likes
0
points
6.32k
downloads

Publisher

verified publishersaropa.com

Weekly Downloads

2134 custom lint rules with 254 quick fixes for Flutter and Dart. Static analysis for security, accessibility, and performance.

Homepage
Repository (GitHub)
View/report issues

Topics

#linter #static-analysis #code-quality #flutter #dart

License

unknown (license)

Dependencies

analysis_server_plugin, analyzer, analyzer_plugin, collection, meta, path, pub_semver, yaml

More

Packages that depend on saropa_lints