saropa_lints 14.2.2
saropa_lints: ^14.2.2 copied to clipboard
2134 custom lint rules with 254 quick fixes for Flutter and Dart. Static analysis for security, accessibility, and performance.
Changelog #
....
-+shdmNMMMMNmdhs+-
-odMMMNyo/-..``.++:+o+/-
/dMMMMMM/ `````
dMMMMMMMMNdhhhdddmmmNmmddhs+-
/MMMMMMMMMMMMMMMMMMMMMMMMMMMMMNh/
. :sdmNNNNMMMMMNNNMMMMMMMMMMMMMMMMm+
o ..~~~::~+==+~:/+sdNMMMMMMMMMMMo
m .+NMMMMMMMMMN
m+ :MMMMMMMMMm
/N: :MMMMMMMMM/
oNs. +NMMMMMMMMo
:dNy/. ./smMMMMMMMMm:
/dMNmhyso+++oosydNNMMMMMMMMMd/
.odMMMMMMMMMMMMMMMMMMMMdo-
-+shdNNMMMMNNdhs+-
``
Made by Saropa. All rights reserved.
Learn more at https://saropa.com, or mailto://dev.tools@saropa.com
2100+ custom lint rules with 250+ quick fixes for Flutter and Dart — static analysis for security, accessibility, performance, and library-specific patterns. Includes a VS Code extension with Package Vibrancy scoring.
Package — pub.dev/packages/saropa_lints
Releases — github.com/saropa/saropa_lints/releases
VS Code Marketplace — marketplace.visualstudio.com/items?itemName=saropa.saropa-lints
Open VSX Registry — open-vsx.org/extension/saropa/saropa-lints
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) flagsAnimatedSizeplaced directly inside aWraporFlow. That combination throws "RenderAnimatedSize was mutated in its own performLayout" every frame once the size animates, becauseWrap/Flowlay each child out within their own measurement pass whileAnimatedSizere-dirties itself. Move theAnimatedSizeinto aColumn/ListView, or put a bounded box (SizedBox/ConstrainedBox) between the two.
Maintenance
- Excluded the regenerated Dart
buildoutput from VS Code's file watcher in.vscode/settings.json. VS Code does not skipbuild/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.autocrlfis 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.darttolerate the transient Windows file lock. On Windows the analyzer briefly keeps file handles open after a scan, so the teardown's immediatedeleteSyncintermittently failed withPathAccessException(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, socontactSaropaUUIDbecomes the SQL columncontact_saropa_u_u_i_d— not thecontact_saropa_uuida 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_urlsnow flags hardcoded URLs on anapi.host, not just/apipaths. The detection pattern previously required/apiin the URL path, so the most common shape — anapi.subdomain with an ordinary path such ashttps://api.example.com/users— slipped through and the rule missed its own documented bad example. URLs with neither anapi.host nor an/apipath 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 theexpect_lintfixtures 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, andavoid_shrinkwrap_in_scrollviewall policed the sameshrinkWrap: trueconcern, so one site drew up to four diagnostics and an acknowledgment under one rule name did not suppress the others; the canonicalavoid_shrink_wrap_expensiveflags nested and non-nested cases alike while exempting the safeNeverScrollableScrollPhysicspattern. Deprecated rules are dropped from freshly generated tier configs — re-run init or write-config to clear them, or remove them fromanalysis_options.yamlby hand.
Fixed #
prefer_static_final_for_session_constantno longer flagsThemeCommonSizeorThemeCommonFontSizearithmetic. Those getters fold the avatar-scale preference and the system text scale, so hoisting them to astatic finalwould freeze a value the user can change and show a stale UI; the rule now treats onlyThemeCommonSpaceas session-constant. No action required.prefer_boolean_prefixesno longer flags boolean fields whose name is a serialization or schema contract. Fields on an Isar@collection/@embeddedclass, a Drift@DataClassNamerow, or carrying@JsonKeymap 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
unusedflag are now resolved per declaration, not matched by name. Previously every function sharing a name pooled into one count, so a heavily-used_disposemade every other_disposelook 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@overridelifecycle hooks) are no longer mislabeledunused. 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 (
diovsdio 5.x, Riverpod 2 vs 3,app_linksvsapp_links 6.x, and similar) now carry a "Pick one version" tag and are mutually exclusive — enabling one variant turns its siblings off, andrule_packs.enabledcan 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.tsinto 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 inbuild()built only from session-constant operands (literals,constfields, and design-token getters such asThemeCommonSpace.Footer.size) that recompute on every rebuild; hoist them to astatic finalfield, which—unlikeconst—works because the token getters resolve at runtime. Bare single getters and anything depending oncontext,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 getplus 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-infosflagged (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_permissionflags exact-alarm scheduling when the manifest declares neitherSCHEDULE_EXACT_ALARMnorUSE_EXACT_ALARM. Android 14 (API 34) denies the exact-alarm capability by default, so azonedSchedule(..., androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle)(orAndroidAlarmManager.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_permissionsuggestsREAD_MEDIA_VISUAL_USER_SELECTEDwhen broad media permissions are declared. When the manifest already declaresREAD_MEDIA_IMAGES/READ_MEDIA_VIDEOand 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_wasmflagspackage:jsimports, which breakflutter build web --wasm.package:jshas no WebAssembly implementation, so its presence fails the Wasm build; the supported replacement is the built-indart:js_interop. Distinct from the existingdart:js/dart:htmlrules, which do not cover the third-partypackage:js. Comprehensive tier (Wasm is opt-in). - New rule
avoid_platform_incompatible_dependencywarns 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 aweb/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.hostssetting. 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 example192.168.1.151) to probe in order; each is scanned across the port range, or pin an exact endpoint withhost: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 agoogle-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_overridesentry 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_indicatorsno 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 aContainerin isolation, so a thin active-tab underline whose color toggles toColors.transparent(a presence cue, not a red-vs-green hue) and a colored swatch sitting next to a siblingText/Iconthat reacts to the same state (bold weight or distinct glyph) were both reported as inaccessible. The rule now suppresses both: a branch resolving toColors.transparentis 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 (aRowwith a conditionalIconbeside the coloredContainer). Genuine standalone red/green indicators with no secondary cue still warn. No action required.
Maintenance
- When
dart pub get/dart pub depsfails 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 withdart 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 thestatic/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
analyzerconstraint guard inpubspec.yaml. Theanalyzer: ^12.1.0cap now carries a boxed HARD STOP note documenting that analyzer 13 renamed the public AST classes the package is built on (NamedExpression→NamedArgument,SimpleFormalParameter→RegularFormalParameter,DefaultFormalParameterremoved) with no deprecated aliases — a bump is a ~912-reference source migration across ~148 files, on top of the existingmeta ^1.18.3Flutter-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_scrollno longer fires on scroll views that contain no text field. The rule now warns only when aListView/GridView/CustomScrollView/SingleChildScrollViewactually 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_mutationno 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) andsetupParentData(RenderObject child)(assigningchild.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 foravoid_unassigned_late_fieldsandavoid_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-awareintlformatting, plurals/RTL),documentation(public-API dartdoc completeness), andtesting(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 underrule_packs.enabledinanalysis_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 --resolveruns the standalone scanner with full type resolution. Rules that fire on constructor calls likeFile('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'spub get; the default fast path is unchanged.
Changed #
- The rules in the new
ui_excellence/localization/documentation/testingpacks 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 orrule_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 explicitfalseindiagnostics:still wins over pack opt-in.
Fixed #
- The
scanCLI 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; runscan --resolve(or check via the IDE/custom_lintplugin, 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
scanrun. 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_callsno 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_fieldsno longer fires on a field initialized by arequired this.field(or optionalthis.field/super.field) named parameter. Named and optional parameters wrap the initializing formal in aDefaultFormalParameter, 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 positionalthis.fieldparameters were recognized before; now named, optional-positional, and super formals are all treated as assigning the field.avoid_throw_objects_without_tostringno longer fires onthrow Error.throwWithStackTrace(obj, stack)whenobj's class has a usefultoString(). That call always returnsNever, so the rule was checkingNeverinstead of the actually-thrown object and flagging every such throw. It now inspects the first argument's type, and recognizes atoString()override inherited from any non-Objectsuperclass (not only one declared directly on the thrown class). Genuine violations — a thrown class with notoString()— are still reported, including when thrown viathrowWithStackTrace.prefer_final_fieldsno longer suggestsfinalfor a field that is reassigned from another class, and is now limited to private fields. It previously counted onlythis-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. Useprefer_final_fields_alwaysto flag every non-final field.move_variable_outside_iterationno 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'sdir = dir.parent, aforcounter, aj++), 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_checkno longer fires ondart:iousage inside the native branch of a conditional import or export (the*_io.dartfile selected byif (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 nokIsWebcheck is needed. The rule now applies the same conditional-import suppression the siblingprefer_platform_io_conditionalrule already used; the underlying scanner additionally recognizes conditionalexportdirectives (not onlyimport) and the*_io.dart/*_stub.dartnaming 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_checkno 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 exampleavoid_recursive_callspointing at the inner self-call of a multi-linereturn— 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.pyhelper 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 atscripts/.shared/reports_organizer.pyand 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_shufflerule (Recommended tier). Flags(collection..shuffle()).firstand similar, where..shuffle()is cascaded onto a stored list whose result is consumed, becauseshuffle()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 letspub getresolve against an untested future SDK major.avoid_unbounded_dependency(Recommended) flags dependencies pinned toany.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.3over the equivalent>=1.2.3 <2.0.0, andavoid_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 theapp_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, andrequire_speech_stop_on_disposenow 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, andconfigSuggestions.badgeTooltipwere 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
isGeneratedDartPathhelper 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 ageneratedpath 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.tsgained the spacing / radius / type / elevation / motion / z-index tokens (plus an exportedgetDashboardTokens()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-secondaryBackgroundundefined 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-glowvalue now matchchromeTokens()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.openDashboardscommand 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
mainno longer triggers a redundantcirun. 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/testWidgetsstubs 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 likeapp_links,image_picker,webview_flutter,url_launcher,geolocator,google_sign_in,firebase, plus the six new packs above). If youranalysis_options.yamllistspackage_specificunderrule_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_constructorno longer flagslist.add(...)orcontroller.add(...)in a Bloc constructor. It flagged every method namedadd, so populating a local list or stream controller during construction was wrongly reported as dispatching a Bloc event; it now flags only an unqualifiedadd(event)on the Bloc itself. No action required. -
avoid_ref_in_disposeandavoid_ref_inside_state_disposeno longer flag an unrelated field or local namedrefused indispose(). They reported anyrefby name, so a non-Riverpod object namedrefwas wrongly flagged; they now report only whenrefresolves to Riverpod'sWidgetRef/Ref. No action required. -
avoid_nullable_async_value_patternno longer flags.valueon a variable merely named with "async". It matched the variable name, so anyasyncThing.valueon an unrelated type was wrongly reported; it now flags only.valueon a realAsyncValue. No action required. -
require_immutable_bloc_stateno longer flags a...State-named class that is not a Bloc state. Any concrete class whose name ended inState(such as aRequestStatevalue object) was flagged purely by name; it now fires only when the class is used as aBloc/Cubitstate type argument. No action required. -
prefer_cubit_for_simplenow counts event handlers from the code, not from comments or strings. The handler count came from a text scan that matchedon<...>tokens inside comments and string literals and missed nested generics likeon<Wrapper<int>>; it now counts realon<Event>(...)registrations, fixing both over- and under-counting. No action required. -
avoid_logging_sensitive_datano longer flags logging an OAuth flow identifier such asoauthToken. The intended OAuth carve-out never worked and the rule treated thetokeninsideoauthTokenas a leaked secret; it now suppresses only when the sensitive word is part of a known safe identifier. No action required. -
prefer_streaming_responseno longer flags.bodyor in-memory uses of.bodyBytes. It also fired on the String.bodyand on any.bodyBytesthat merely shared a scope with an unrelatedfilevariable; it now flags only.bodyByteswritten to disk in the same function. No action required. -
avoid_over_fetchingno longer flags ordinary repository methods that fetch and return a single field. A short method likefinal 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_refreshno 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_clientno longer flags constructing a JWT model object outside an authorization check. Building a value likeJsonWebToken.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_fallbacknow catches the canonicallocalAuth.authenticate(biometricOnly: true)call. The receiver-name match missed single-token receivers likelocalAuth, so biometric-only logins went unflagged; it now matches the local_auth biometric API. No action required. -
avoid_webview_javascript_enabledno longer fires when JavaScript is disabled but an unrelated flag (e.g.isInspectable: true) is set. The rule now reads the actual boolean value ofjavaScriptEnabledinstead of scanning the argument text fortrue, soInAppWebViewSettings(javaScriptEnabled: false, isInspectable: true)stays silent. No action required. -
prefer_cached_getterno 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-declaredgetaccessor; 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_listnow flags only StatefulWidget list items,avoid_duplicate_widget_keystreatsKey('x')andValueKey('x')as distinct,avoid_gesture_conflictrequires direct nesting,prefer_tap_region_for_dismissno longer trips onpopulate/closest,prefer_asset_image_for_localmatches onlyassets/paths,avoid_nullable_widget_methodsno longer flags non-widget types named "…Widget",avoid_double_tap_submitno longer treats a "Reorder" button as submit,avoid_late_without_guaranteedetects a realinitStateassignment,avoid_static_route_configmatches exact router types, andprefer_split_widget_constcounts only const-constructible children. Fewer spurious warnings; no action required. -
avoid_string_concatenation_loopno longer flags numeric+=accumulators. A loop body liketotal += countorresultMap[k] += nwas reported as O(n²) string concatenation based only on the variable name containingresult/output/buffer/message. The+=branch now requires the accumulator to actually be aString(by resolved type), so numeric accumulation stays silent while genuineString += ...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, andavoid_set_state_in_disposewere silently dead — they tested a method's direct parent forClassDeclaration, 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_initstateandavoid_expensive_did_change_dependenciesno longer fire on a same-namedinitState/didChangeDependenciesmethod declared on a plain (non-State) class.avoid_unsafe_setstateno longer treatssetState()in theelsebranch ofif (mounted)as guarded.prefer_widget_state_mixinno longer counts unrelated members like_unfocusTimer/_focusableItemsas interaction-state fields (whole-word, bool-typed match).avoid_scaffold_messenger_after_awaitwalks the body in source order so aScaffoldMessenger.of(context)call lexically before the await is not reported as "after".require_field_disposedetects 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_disposeskips controllers read fromwidget.*(parent-owned).avoid_recursive_widget_callsmatches self-instantiation by resolved element, so a same-named imported widget is no longer flagged.require_init_state_idempotentmatchesremoveListener/removeObserveras 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_misuseflags only a state-toggled ternary, so a static const such asopacity: _kDisabledOpacitystays silent;prefer_fractional_sizingmatches a realMediaQuery…size.width|heightread by structure, so a same-named local likefakeMediaQuery.size.width * 0.5no longer trips;prefer_page_storage_keyrecognizes aPageStorageKeyby type and is no longer fooled by lookalike names;avoid_stack_without_positionedskips children of aStackthat sets its ownalignment/fit(the intentional badge-over-avatar pattern);prefer_spacing_over_sizedboxtreatsSizedBox(height: 8)and8.0as the same spacer;avoid_builder_index_out_of_boundscompares full receiver chains so a guard onother.items.lengthno longer excusesitems[index], catching a real out-of-bounds risk; andavoid_hardcoded_layout_valuesdrops a dead duplicate branch with no behavior change. No action required. -
require_stream_error_handlingno longer flags a non-Stream object whose name ends in "controller". A call likeanimationController.listen(...)was reported for a missingonErrorbecause the name-based fallback treated any "…controller" receiver as a stream. It now requires the resolvedStreamtype, an explicit.streamaccess, or a name ending in "stream"; a bareAnimationController/ScrollControllerstays silent. No action required. -
prefer_utc_for_storageno longer flags atoIso8601String()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 anysave(/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_invocationsno longer flags an unrelateddart:iocall such assystemEncoding.decode(...). A catch-all final clause matched anydart:ioelement whose method name appeared in the throwing-call set, so non-I/O members nameddecode/parsewere wrongly reported; detection is now restricted to the documented throwing calls qualified by both library and method (dart:ioread*/write* sync I/O,dart:convertdecode/jsonDecode,dart:coreparse). No action required. -
avoid_dialog_context_after_asyncno longer misjudges amountedcheck 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 afterNavigator.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 latermountedcheck is correctly flagged. No action required. -
prefer_no_commented_out_codeno longer flags a wrapped prose sentence that cites a function call. A multi-line comment such as "…Without this,formatNumberLocale(x, decimalPlaces: 25)crashed (formatDoublein…" 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 containingprefix,suffix,fixture, ortoStringAsFixedbeing misread asFIXtask markers (marker words now match only as whole words), which also affectsprefer_capitalized_comment_start. No action required. -
prefer_setup_teardownno 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 intosetUp()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_guardsno longer fires on routes whose path merely contains a protected word. This ERROR-level rule treated/reorderas the protected segmentorderand/accountingasaccount(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.accountoradminis flagged. No action required. -
avoid_go_router_push_replacement_confusionno longer flags non-detail routes by substring.context.go('/viewport/$x')matched the detail segment/viewand was reported; matching is now per path segment, so/viewportno longer trips theviewrule. No action required. -
avoid_push_replacement_misuseno longer fires on builder bodies and route objects. It lowercased the whole argument source, soMaterialPageRoute(builder: () => ListView())matchedviewand anOrder…builder matchedorder. It now inspects only the route name (thepushReplacementNamedstring andRouteSettings(name:)) by segment. No action required. -
require_deep_link_testingno longer treatsuuid:/valid:as an id. The rule checked whether the route argument source containedid:, so an object built with auuid:orvalid:argument was wrongly considered to carry a routable id and skipped. It now inspects argument labels and map keys for an exactid, so objects lacking one are correctly flagged. No action required. -
avoid_pop_without_resultno longer confuses similarly-named variables. Aresultfromawait Navigator.push(...)was considered "used without a null check" when an unrelatedresultValueappeared 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_constraintsno longer flags two legitimateMediaQuerysizing patterns. It fired on aMediaQuery.sizeOfread that is the fallback branch of a constraint-finiteness guard (constraints.maxWidth.isFinite ? constraints.maxWidth : MediaQuery.sizeOf(context).width) — code that already usesLayoutBuilderand only consultsMediaQuerywhen the parent passes unbounded constraints, where there is noLayoutBuildervalue to use. It also flagged a screen-fraction height scaled by a named factor (MediaQuery.sizeOf(context).height * fraction) inside an unbounded scroller, whereLayoutBuilderwould 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 genuinewidth: MediaQuery.sizeOf(context).widthsizing keeps flagging. -
Line-level
// ignore:no longer over-suppresses rules whose name is a prefix of another. An// ignore: my_rule_extendedcomment was matched by a bare substring check, so it also silenced the distinct, shortermy_ruleon 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.dartwas matched againstmy_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_tostringno longer fires onFutureOrvalues, 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 withFuture— which wrongly flaggedFutureOr<T>(a false positive on.toString()/interpolation) and missed types that implementFuture. They now use a proper type check (Future or a Future subtype, excludingFutureOr), removing the false positives and catching previously-missed Future subtypes. -
avoid_expensive_buildno longer flags cheap built-in conversions inbuild().int.parse,double.tryParse,DateTime.parse, andUri.parsewere matched purely by the method nameparse/tryParse, so these common, inexpensive calls were reported as expensive build-time work. The rule now skipsparse/tryParsewhose resolved result is a core primitive while still flagging heavy parsing such asjsonDecode. -
incorrect_firebase_event_nameno longer fires on non-Firebase analytics. This ERROR-level rule flagged anylogEvent(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_indexandrequire_database_migrationno 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_storageno longer fires without a Drift import. It flagged anyWebDatabase(...)constructor or any method namedunsafeIndexedDb, even in code that does not use Drift; it now requires adrift/drift_flutterimport like every other Drift rule. -
avoid_throw_in_catch_blocknow 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, userethrow/Error.throwWithStackTraceor disable the rule. -
Android permission checks no longer match a longer permission that shares a prefix. A check for
READ_CONTACTSalso matched a manifest declaring onlyREAD_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/locationstring.hasIosBackgroundAudioConfigured/...LocationConfiguredchecked for the mode string anywhere in Info.plist, so a plist enabling only backgroundlocationthat mentionedaudioelsewhere reported audio as enabled. The mode is now matched inside theUIBackgroundModesarray.
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_libs→isar_community/isar_community_flutter_libs,hive_flutter→hive_ce_flutter), the last Flutter Community Plus gap (wifi_info_flutter→network_info_plus), discontinued packages pub.dev itself names a replacement for (the AngularDart packages →ng*,uni_links→app_links,artemis→ferry,super_enum→freezed, and more), and abandoned widgets/plugins (flutter_web_auth→flutter_web_auth_2,pdf_render→pdfrx,nfc_in_flutter→nfc_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: trueoverride; 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, andsaropaLints.openFindingcommands 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_advisorbut 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_stylestuck because another dependency pinsanalyzerlow, or a package pinned by the Flutter SDK's exactcharacters/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. -
constrainedpackages now name the constraint holding them back. A package the resolver could lift but your own pubspec caps now shows "your constraint^xcaps this —yresolvable" 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.siblingRepoPathsto other repo folders and a package pinned at a lower major than a sibling (e.g.saropa_lints ^9.7.0here vs^13.12.7elsewhere) 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.watchIntervalHoursto0(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_lintsdogfoods itself,dart analyzereports the plugin dependency as a constraint (^14.0.0) whilepubspec.yamlholds 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 ownanalysis_options.yamlhas no pluginversion: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 anytest/testWidgetscarrying askip:argument; a genuine() {}stub with noskip: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-nowtest 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/andbin/for real import/export directives and fails if an imported package is absent from pubspecdependencies. This catches the class of defect that sank v13.12.6 and v13.12.7 (ametaimport with nometadependency):lib/**is inanalyzer.excludefor plugin dogfooding, so nodart analyzerun inspects these imports, anddart pub publishonly rejected them on the post-tag CI job — after the tag was burned. The gate is deterministic and Dart-version-independent, and ignorespackage: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 theanalyzejob, so a missing dependency fails at merge time rather than at release. The pre-existingdart pub publish --dry-runCI 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 uxrenders 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,
buildDetailCardand 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 watchstep 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. Adependencies: # commentline 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_versiononly 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_sectionandrename_unreleased_to_versionused 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.yamlplus 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.dartruns 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 countedexpect_lintstrings, 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/showErrorMessagecalls (and their action-button labels) across the extension were routed throughl10n()with{token}interpolation instead of hardcoded English or string concatenation — 208 keys under a newnotify.*namespace inen.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, Indonesianinfo, 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.pyrelaunches under the standardpython.exebeside 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 underpython3.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.pynow 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
esbuilddevDependency to 0.28.1 to clear GHSA-gv7w-rqvm-qjhr. The advisory's RCE only affects esbuild's Deno install path via a maliciousNPM_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-quotecritical advisory (GHSA-w7jw-789q-3m8p) in the extension's dev dependency tree. A transitive dev-only dependency was bumped vianpm audit fix; it is build/test tooling and never ships to users. Two remaining low-severitydiff-via-mochaadvisories 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.