saropa_lints 13.11.9 copy "saropa_lints: ^13.11.9" to clipboard
saropa_lints: ^13.11.9 copied to clipboard

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

Changelog #

                                    ....
                             -+shdmNMMMMNmdhs+-
                          -odMMMNyo/-..``.++:+o+/-
                       /dMMMMMM/               `````
                      dMMMMMMMMNdhhhdddmmmNmmddhs+-
                      /MMMMMMMMMMMMMMMMMMMMMMMMMMMMMNh/
                    . :sdmNNNNMMMMMNNNMMMMMMMMMMMMMMMMm+
                    o     ..~~~::~+==+~:/+sdNMMMMMMMMMMMo
                    m                        .+NMMMMMMMMMN
                    m+                         :MMMMMMMMMm
                    /N:                        :MMMMMMMMM/
                     oNs.                    +NMMMMMMMMo
                      :dNy/.              ./smMMMMMMMMm:
                       /dMNmhyso+++oosydNNMMMMMMMMMd/
                          .odMMMMMMMMMMMMMMMMMMMMdo-
                             -+shdNNMMMMNNdhs+-
                                     ``

Made by Saropa. All rights reserved.

Learn more at https://saropa.com, or mailto://dev.tools@saropa.com

2100+ custom lint rules with 250+ quick fixes for Flutter and Dart — static analysis for security, accessibility, performance, and library-specific patterns. Includes a VS Code extension with Package Vibrancy scoring.

Packagepub.dev/packages/saropa_lints

Releasesgithub.com/saropa/saropa_lints/releases

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

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

13.11.8 #

Fixed #

  • avoid_nullable_interpolation no longer fires on ${x ?? fallback}, on syntactic if (x != null) / x != null ? ... : ... guards over chained property access, or on developer-facing log calls (debug, breadcrumb, debugPrint, print, dart:developer log). Enabling the rule on a real Flutter codebase produced floods of false positives at sites where the developer had already handled null or was intentionally logging it for diagnosis (one project saw 58 raw hits across 22 sites, half of them inside debug() strings where seeing null IS the point). Remove any project-local // ignore: avoid_nullable_interpolation comments added for these three patterns.

13.11.7 #

Fixed #

  • avoid_expensive_build no longer fires on iteration primitives (sort, where, map, fold, reduce) inside build(). These calls are the idiomatic shape of list-to-widget rendering (Column(children: items.map(...).toList()) is the Flutter cookbook pattern) and produced noise without measurable cost on typical UI list sizes — one production project saw 45 WARNING hits on the first run, every one a small-list render. The rule now flags only genuinely heavy operations: jsonDecode, jsonEncode, parse, tryParse, compute, and the readAsXxx file-I/O family. Remove any project-local // ignore: avoid_expensive_build comments added for the iteration patterns above.

13.11.6 #

Fixed #

  • avoid_large_list_copy no longer fires when the .toList() is structurally required by a named argument, a ?? fallback, or a getter on the result. Named arguments like children: items.map(...).toList(), null-coalescing chains like source?.where(...).toList() ?? <T>[], and getter access like items.map(...).toList().nonEmpty all need a concrete List and have no lazy alternative — remove any // ignore: avoid_large_list_copy added for these three patterns.
  • avoid_listview_without_item_extent no longer fires on ListView.separated. The .separated constructor does not declare itemExtent, prototypeItem, or itemExtentBuilder (an extent applied to items would also apply to separators, which never share one), so the rule's correction was unfixable on .separated. The rule now targets ListView.builder only — remove any // ignore: avoid_listview_without_item_extent added on .separated call sites.
  • avoid_listview_without_item_extent no longer fires on inline non-scrolling ListView.builder. When the call sets both shrinkWrap: true and physics: NeverScrollableScrollPhysics() the inner list does not scroll and shrinkWrap already forces eager layout, so the extent-hint guidance does not apply and forcing one would clip variable-height rows — remove any // ignore: avoid_listview_without_item_extent that was added for this pattern.

13.11.5 #

Added #

  • Code Health scanner auto-skips generated files. Filename suffixes .g.dart, .freezed.dart, .mocks.dart, .gr.dart, .config.dart, .chopper.dart, .gen.dart, .drift.dart, and the protobuf family (.pb.dart, .pbenum.dart, .pbgrpc.dart, .pbjson.dart, .pbserver.dart) are excluded at file-walk time; gen-l10n output (lib/l10n/app_localizations*.dart, lib/l10n/intl_*.dart) is skipped by path. A header-marker fallback (GENERATED CODE, DO NOT MODIFY, DO NOT EDIT, AUTO-GENERATED FILE in the first 1KB) catches codegen output that lands at non-standard paths. The hero band shows an N suppressed pill so suppression is visible, never silent.
  • // ignore_for_file: code_health directive support. Add // ignore_for_file: code_health at the top of a Dart file to keep it out of the Code Health scan entirely; add // ignore_for_file: code_health:complex,undocumented to drop only specific flags from every row in that file. The directive composes with normal analyzer ignore lists (// ignore_for_file: avoid_print, code_health is recognized).

Added (Extension) #

  • "Suppress in file" button on every issue in the Code Health Dashboard's expanded detail row. Click writes a // ignore_for_file: code_health:<flag> directive at the top of the source file (merging with any existing ignore list — sorted, deduped, idempotent) and offers a one-click Rescan. Files-not-yet-saved are respected via VS Code's workspace file API.

Changed (Extension) #

  • Code Health Dashboard "Worst functions" rows now explain WHY each function scored low. Flag pills (unused, complex, undocumented, …) used to carry only the label, so a reader had to look at four other columns to reconstruct the evidence. Each pill now reads complex (CC 36), unused (0 callers), uncovered (0% tests) etc. — the threshold the row tripped, inline. A chevron next to the score expands the row to show every issue with its threshold rule (e.g. "Flagged when cyclomatic complexity exceeds 10"), so readers can confirm what to fix without leaving the table. Expand state survives sort and filter changes. No action required.
Maintenance
  • Publish script no longer hard-aborts when the extension locale coverage gate fails. The user is now prompted Ignore / Retry (default) / Abort, so the typical recovery — edit extension/scripts/i18n/dictionaries.py and rerun the generator — happens in-place without restarting the whole publish.

13.11.9 #

Changed #

  • Release version

13.11.4 #

Added (Extension) #

  • Saropa Package Dashboard hero now shows a "Scanned X ago" pill with the actual scan timestamp. Hovering reveals the absolute date/time. Previously the Rescan button could feel like a no-op because the dashboard re-rendered with identical numbers and no recency indicator, so users had no way to tell whether the click triggered a fresh scan or returned cached results. The pill updates on every scan completion (including the trailing rescan that fires after a coalesced in-flight scan), so a click producing fresh data flips "5m ago" back to "just now". No action required.

Fixed (Extension) #

  • Package Dashboard no longer drops direct dependencies that follow a zero-indent comment. A # cspell:ignore foo (or any other column-zero comment) inside dependencies: or dependency_overrides: was treated as a new top-level YAML key, exiting the active section and silently skipping every package declared after it. On the saropa contacts pubspec two # cspell:ignore … comments caused ~14 direct deps (device_info_plus, image, share_plus, youtube_player_flutter, …) to vanish from the dashboard and the saved pubspec_vibrancy.json. The parser now ignores comment and blank lines in the section-exit guard. A real top-level key still ends the section. No action required — rescan to repopulate.
  • Package Dashboard Total Size and Size Distribution now exclude dev_dependencies. Dev-only tooling like saropa_lints, build_runner, and lints was being summed into the "Total Size" card and given its own bar in the Size Distribution chart, even though dev deps never reach the APK / IPA / web bundle. On projects that use saropa_lints as a dev dep, the chart was assigning it ~66% of "total size" — the opposite of what either surface communicates. Both now drop dev deps unconditionally; the "Include dev" toggle still controls the package table. A new caption under the chart and updated Total Size tooltip call out the exclusion. No action required.

Fixed #

  • require_error_widget no longer fires when error handling lives in an extension method on AsyncSnapshot. Centralized helpers like snapshot.snapLoadingProgress() (returns the loading widget or null) and snapshot.reportErrorIfAny() were reported as missing error handling because the substring check at the call site never saw the literal hasError / .error text — the inspection happened inside the extension. The rule now walks the builder body and treats an inline .hasError / .error / .stackTrace access, any method invocation on the snapshot parameter, or any helper whose name itself contains "error" as sufficient. The same change also closes a latent false negative where a local variable named hasErrorState suppressed the lint via raw substring match. Remove any project-local // ignore: require_error_widget comments added to silence the false positive.
  • require_late_initialization_in_init_state no longer fires on reassignment inside onPressed, onTap, or setState callbacks. Standard "View All / load more" patterns — a late field that is correctly initialized in initState and then reset to a different value inside a button or gesture callback — were incorrectly reported as build-path initialization because the rule matched the assignment via a regex over the build method's source text and could not see that the assignment was lexically nested inside a closure. The rule now walks the AST and skips assignments inside nested function expressions, and excludes any late field that initState already assigns. Remove any project-local // ignore: require_late_initialization_in_init_state comments added to silence the false positive.
  • pass_existing_future_to_future_builder no longer fires on the cache-method pattern. When the future: argument is a private instance method (_getContactsFuture(...)) on a class that declares at least one Future<...>? field, the rule now treats the call as a cached-future accessor rather than a fresh allocation. This is the idiomatic pattern when the cached future depends on dynamic input that a late final field cannot model, and the rule's own correction message ("cache the Future") already endorses it. Public methods and methods on classes without a nullable Future field still fire. Remove any project-local // ignore: pass_existing_future_to_future_builder comments added to silence the false positive.

13.11.3 #

Fixed #

  • function_always_returns_null no longer fires on @override declarations that return null. Overriding a nullable parent member (e.g. @override Color? get barrierColor => null; on a no-barrier ModalRoute) honors the parent's contract — the override cannot widen the return type, and returning anything other than null would lie about absence. The rule now skips any declaration carrying @override; if the parent's return type were non-nullable, the override would already be a compile error, so the annotation alone is a sufficient signal. No action required.
  • avoid_small_touch_targets no longer false-fires on wide-band overlays. A SizedBox or Container whose single small axis (e.g. height: 38) wrapped a GestureDetector, InkWell, or InkResponse — typically a dismiss-on-tap pill, list row, or Positioned.fill overlay — was incorrectly flagged as a small touch target even though the tap region spans the full parent width. The rule now distinguishes icon-sized targets (IconButton, Checkbox, Radio, Switch, TextButton, ElevatedButton, OutlinedButton) — where either axis under 44 px is a real concern — from region recognizers, which require both axes explicitly under 44 px to fire. Remove any project-local // ignore: avoid_small_touch_targets comments added to silence the false positive.
  • prefer_layout_builder_for_constraints no longer fires inside static utility methods that take a BuildContext. Static helpers like MenuUtils.popupMenuConstraints(BuildContext) compute absolute viewport-fraction dimensions for non-widget return types (BoxConstraints, Size, EdgeInsets); LayoutBuilder is structurally inapplicable to them because there is no parent constraint to consult and the return is data, not a widget. Instance methods that take BuildContext (the 2026-04-28 case) still fire. Remove any project-local // ignore: prefer_layout_builder_for_constraints comments added to silence the false positive on static utilities.
Maintenance
  • Python publish-tooling tests now find the repo root. The unittest suite was relocated to scripts/modules/tests/ in May without updating its parents[2] repo-root index, leaving CI's test job red against every release commit since (including Release v13.11.2). No user impact.
  • Removed post-publish auto-bump of pubspec.yaml. Releases no longer auto-commit chore: bump version to n.n.n+1. The next publish prompt now defaults to a patch bump only when CHANGELOG.md has an [Unreleased] section, so minor or major releases no longer have to undo a pre-committed patch decision and main no longer carries phantom version-bump commits for releases that never shipped. No user impact.
  • Stopped per-release churn of views.help.name across 25 locale bundles. The static value is now the localized word "Help" alone (no version suffix); the runtime createTreeView().title injection in extension/src/extension.ts continues to display the live (vX.Y.Z) from package.json, unchanged from the user's perspective. The pre-compile sync script that stamped the version on every locale was deleted, which also fixes a long-standing duplicate-version pattern that had crept into Arabic, Persian, and Polish bundles (the stripper only matched lowercase Latin v, leaving translated version parens intact across releases). No user impact.

13.11.2 #

Fixed #

  • Analysis reports no longer pile up — one file per VS Code session, overwritten in place — every file save was previously generating a new *_saropa_lint_report.log (one observed project had 6,837 trashed reports / 659 MB in reports/.trash/), violating the reporter's documented contract; the reporter now overwrites the same file on each debounce cycle and also prunes trashed reports older than 14 days. No action required.

13.11.1 #

Stub build for publishing. No recorded changes.


13.11.0 #

This release ships a new Saropa Project Map dashboard — a project-wide health scan that walks your code and ranks the worst files across complexity, coverage, dead code, churn, coupling, and import cycles, then prints a prioritized worklist (and copy-paste agent prompts) so you know what to fix first. The Code Health Dashboard also got a major overhaul: it opens instantly with a live progress bar, can be paused/resumed/restarted/canceled, caches per-file results so re-scans are fast, and lets you slice the worst-functions table by score, search, or any combination of flag categories — then bulk-copy what's left to the clipboard. And pubspec validation no longer pesters you about files inside the pub cache or vendored third-party packages. log

Added #

  • Project Health scan (dart run saropa_lints:project_health) — walks a Dart project and reports the largest files and folders by lines (split into code, comment, and blank) and on-disk size. Per-file results stream to a reports/.saropa_lints/health/files.ndjson shard while only bounded aggregates stay in memory, so it stays fast and memory-flat on very large projects. Building blocks of the Saropa Project Map dashboard.
    • --complexity adds per-file cognitive and cyclomatic complexity, local-variable and boolean-condition density, nesting depth, class cohesion (LCOM), and a 0–100 Maintainability Index — all from a single parse per file (no element resolution, so memory stays flat).
    • --deadweight flags unused files and dead top-level symbols (composing the existing cross-file engine); --coverage/--lcov reads line coverage; --git adds per-file churn, recency, and a bus-factor proxy from history (bounded for large repos).
    • --assets flags pubspec assets/fonts declared but never referenced in code (heuristic — report-only, verify before deleting).
    • --islands finds transitive dead private islands — private declarations that reference each other but are unreachable from any live root, the case Dart's unused_element misses.
    • --coupling surfaces change-coupled file pairs (files that keep changing together in git history), and --stubs flags tests with no assertions.
    • --fix (with --deadweight) writes a reviewable git rm script — never deletes in place, never comments code out.
    • Surfaced in the extension sidebar as a new "Saropa Project Map" dashboard (sixth "Editor dashboards" leaf). The scan runs asynchronously with a cancellable progress notification (never freezes the editor) and renders in-editor with charts drawn by a vendored ECharts bundle (works offline, no CDN/network).
    • --format markdown emits a prioritized, 🔥-tiered "hot spots" worklist — files bad on multiple axes at once (large, complex, low-maintainability, dead, churning, uncovered) — with checkbox actions that name the offending functions (file + line + cognitive score), ready to hand to an AI agent. Dead-weight items carry a "verify before deleting" caveat.
    • --format prompts emits one self-contained, copy-paste-ready agent task per hot spot — the file, the exact functions to simplify (name/line/score), dead symbols, coverage, and churn, plus behavior-preserving constraints — so an AI can fix it without re-deriving the analysis.
    • --update-baseline / --baseline <path> capture a health snapshot and compare against it, reporting what got better or worse and exiting non-zero on regression (CI gate on rising complexity, growing dead code, or falling coverage).
    • Coverage runs now warn when lcov.info predates the latest commit — stale coverage is reported as unverified instead of silently trusted.
    • Optional .saropa_health.yaml config: an allowlist that silences known false positives in the heuristic sections (dead files, dead symbols, islands, assets) plus shared exclude globs — so the report stays trustworthy instead of crying wolf.
    • Refactoring-ROI ranking — a continuous "fix this first for maximum risk reduction" score combining complexity, size, git churn, and under-coverage. Surfaced in the text report and JSON (topFiles.byRoi); a better priority signal than the discrete 🔥 count.
    • Natural-language exec summary + what-if cleanup simulator ("removing the N unused files would delete X lines, Y% of the codebase") in the text/JSON report.
    • Public-API doc coverage (% of public declarations with ///) and import coupling (fan-in/fan-out + instability, with --deadweight) added to the per-file metrics and summary.
    • Health time-machine (--history) — reconstructs the trajectory of size and complexity across recent git tags (via git archive, never touching the working tree).
    • Saropa Project Map webview gains click-a-row-to-open-the-file drill-down, type-to-filter search over the hot-spot table, a hierarchical folder treemap (click a folder to drill in, breadcrumb to go back), and an opt-in in-editor "heat" CodeLens (per-function complexity above functions) — off by default, toggled with "Saropa Lints: Toggle Saropa Project Map Heat CodeLens".
    • --cycles lists import cycles with a suggested cut per cycle (the stable→volatile edge to remove); --cache reuses the parse for unchanged files so rescans are faster.
    • Visual refresh of the HTML dashboard: brand-anchored orange palette (light + dark via CSS vars), a sticky banner with gradient-text title and a relative-time "scanned X ago" chip, KPI cards merged into the banner with a staggered reveal and a heat-tinted highlight on Dead files / Hot spots, a continuous orange ramp on the size treemap plus a gradient legend bar, sticky table head with a brand-colored active-sort indicator, table zebra striping, monospace path column and tabular-figure numerics, skeleton shimmer on charts until ECharts mounts, and an orange :focus-visible ring — all gated by prefers-reduced-motion.
    • Maintainability Index ranking now uses the unclamped score internally, so the worst-of-the-worst files (which all clamp to 0) are ordered correctly.
    • --format html writes a self-contained, theme-aware interactive report (Apache ECharts): a stat-card header, collapsible panels, a size treemap, a churn×complexity scatter, and a sortable 🔥 hot-spot table. Follows the OS/editor light/dark scheme and respects reduced-motion.
    • Also supports --format text|json, --top, and --exclude <glob>.

Fixed #

  • Code Health scan no longer crashes on a file the analyzer can't fully parse — one source file with a parse-level diagnostic (for example an await outside an async function, common while editing) previously aborted the whole scan with an unhandled exception; it now tolerates such files and keeps scanning the rest. No action required.
  • Code Health scan starts faster and can't hang on symlinks — file discovery now skips heavy directories (build, .dart_tool, node_modules, Pods, .git, …) and no longer follows symbolic links, so the scan reaches your lib/ sources quickly instead of crawling generated output, and a symlink loop can't wedge it. No action required.
  • Code Health no longer stalls on huge generated files — files over ~512 KB (e.g. flutter gen-l10n app_localizations*.dart at multiple MB) are skipped; parsing one previously froze the progress bar for many seconds with no health value. The panel also shows the file it is currently processing and a per-phase time estimate, so a slow phase reads as working, not stalled. No action required.
  • require_https_only no longer flags prose mentions of the http:// scheme — user-facing strings that name supported schemes (e.g. 'http:// or https:// URLs are supported.', the Korean equivalent 'http:// 또는 https:// URL만 지원됩니다.' in a Flutter app_localizations_*.dart file, or a bare 'http://' constant) are no longer reported as insecure URLs. A real URL has no whitespace between scheme and host, so strings with whitespace right after http:// (or just the bare scheme) are descriptive text, not network requests. Hardcoded HTTP URLs ('http://api.example.com', Uri.parse('http://…'), http.get('http://…')) still fire. No action required; any // ignore: markers added to work around this false positive can be removed.

Added (Extension) #

  • The Code Health Dashboard now opens immediately and fills in live while it scans — instead of a notification that sat at "scanning…" with no movement, it shows a progress bar, the current file, running counts of files and functions, and a streaming preview of the worst functions found so far, so a long scan never looks frozen. While the scanner is compiling/starting the bar shows an animated indeterminate state rather than a dead 0%. No action required.
  • Pause, Resume, Restart, and Cancel controls on the Code Health scan — a long scan can now be suspended and continued, restarted, or stopped from the dashboard, and closing the panel also stops it, so the scan no longer runs unstoppably in the background. Canceling now stops the whole scanner process tree (previously a runaway scan could keep running on Windows). No action required.
  • The scanning panel shows the extension version and the scanner-engine version — if the project's saropa_lints is too old to report progress, the engine line says so instead of leaving a stuck 0%, making it obvious when the dashboard and the scanned project's package versions don't match. No action required.

Changed (Extension) #

  • Code Health scan readout is easier to read — file and function counts now use thousands separators, and the elapsed/remaining times show minutes and hours (for example 1h 52m) instead of a raw seconds count. The per-phase progress percent shows one decimal place so it visibly moves on a multi-thousand-file phase. No action required.
  • Click a row in the live "problems found" preview to jump to that function — each row opens the file at the function's line in the editor. No action required.
  • File paths in the scan panel keep the filename visible — long paths now collapse the leading directories instead of cropping the end, and the worst-functions rows line up in fixed columns. No action required.
  • The Code Health scan no longer reports third-party code — vendored packages under dependency_overrides/ are skipped, so the worst-functions list stops surfacing un-actionable rows (such as a bare ==) from code you don't own. Operator overrides in your own code are now labeled (operator ==) rather than shown as a bare symbol. No action required.
  • Code Health scans are now incremental — each file's parse is hashed and cached, so a file unchanged since the last scan is not re-parsed, and within a single scan no file is parsed twice across the parse and usage phases. The cache lives at .saropa/project-vibrancy-cache/mvp_cache.json and self-invalidates per-file on content change and globally on engine upgrade; delete the folder to force a cold scan. No action required.
  • Code Health Dashboard report is the full project, not a 200-row slice — the worst-functions table, the search filter, and the KPI tile clicks now operate on every scored function. Previously clicking "Test drift: 974" only showed the handful of test-drift rows that happened to be in the worst-200, which read as a broken filter. The row count above the table now shows showing N of TOTAL truthfully. No action required.
  • Every Code Health scan is saved to a JSON file you can share — written to reports/<yyyymmdd>/<yyyymmdd>_<HHmmss>_saropa_code_health.json under the project root, with a strip under the KPI cards showing the path and Copy / Open / Reveal-in-Explorer buttons. The path is also clickable to copy. No action required.
  • Code Health Dashboard rows redesigned for readability — the redundant grade column is gone (the grade duplicated the score axis); the score is a colored whole-number pill (red→amber→green) with readable black text instead of the previous low-contrast yellow-on-yellow grade pill; function names are clickable and open the file at the line; file paths collapse the directory and keep the basename whole; a new Changed column shows how long ago the file was last touched (3d, 5w, 2mo). No action required.
  • Column sort indicators are now honest — the active column shows an arrow in the actual sort direction; other columns show nothing. Previously every column displayed an up-arrow forever, which read as decoration rather than state. No action required.
  • Header shows the number of problem functions — the hero now carries a N problems chip (functions scoring under 50, i.e. grades D / E / F) alongside the function count and gate status; the duplicated hero gauge (which redundantly displayed the same average score with different rounding) was removed. No action required.
  • complex is now a KPI tile and click-to-filter category — alongside Unused / Uncovered / Stub-tested / Suspicious coverage / Test drift, so high-complexity functions are surfaced and filterable the same way as the other flag classes. No action required.
  • The Code Health Dashboard no longer freezes on large projects — function rows now ship to the webview as a JSON data block and the script renders a 500-row window into the DOM, so a project with tens of thousands of functions opens responsively and column-header sort is instant. The "Show next 500" button at the bottom of the table reveals the next chunk on demand. No action required.
  • Hide boilerplate methods toggle (default ON) — a new toolbar checkbox hides operator overrides (==, <, [], …) and the equality / serialization / dispatch boilerplate (hashCode, toString, noSuchMethod, copyWith, props, fromJson, toJson, fromMap, toMap). These dominate "worst functions" lists in real projects but rarely deserve triage attention; uncheck the box to see them. No action required.
  • Bulk-copy methods for analysis is filter-driven — narrow the Worst Functions table with the new Score ≤ N threshold, the search field, the Hide boilerplate toggle, and the KPI tiles (multi-select — click multiple to combine flags, e.g. unused AND complex), then click Copy filtered (N) to send every visible row to the clipboard as file:line function-name (score, flags), one line per row, ready to paste into a chat or issue. No per-row checkboxes; the filters are the selection. The active-filter strip lists every applied filter (search, score threshold, each flag) with its own dismiss button. No action required.
  • The Score column now explains what it measures — hovering the column header shows the composite formula (40% coverage + 25% usage + 15% age + 15% complexity + 5% documentation), and Usage / Coverage / Complexity / Changed columns gained similar tooltips so a reader doesn't have to guess what each axis means. No action required.

Fixed (Extension) #

  • Pubspec validation and Package Vibrancy diagnostics no longer fire on third-party packages — opening a vendored pubspec.yaml under the pub cache (Pub/Cache/hosted/…, ~/.pub-cache/…) or under .dart_tool / node_modules was producing saropa-pubspec, saropa-sdk, and Package Vibrancy diagnostics on files you can't edit, polluting the Problems panel. The listener now skips any pubspec outside the open workspace and any pubspec inside a known cache or generated directory. No action required.
  • "Score dipped below N" toasts no longer repeat on every save when the score oscillates near a band edge, and no longer fire from one-off partial-sweep dips — each crossing of a band (90 / 80 / 70 / 60 / 50) now fires at most once and only re-arms after the score recovers clearly above that band (5 points), and a downward crossing is held one snapshot before firing so an intermediate partial sweep that briefly skews the score (the case where the dashboard reads 93 while a stale toast had already said "below 50") is dropped quietly when the next snapshot recovers. Set saropaLints.regressionNudge.enabled to false to silence regression toasts entirely.
  • "No errors!" celebration toast no longer repeats when the error count flickers between 0 and 1 — the toast is now persisted per workspace and only re-fires after errors return and are cleared again, so an intermediate analyzer batch that briefly drops and re-adds an error stops producing a stream of identical celebrations. No action required.
Maintenance
  • When the extension runs as its own in-development build (F5 from the repo), the Code Health scan now executes the in-repo saropa_lints CLI against the opened project (via --path) instead of the project's pinned package version, so new CLI behavior can be tested without a path override. Installed builds are unaffected — they still use the project's own CLI.
  • git blame in the vibrancy scan now passes the scanned project as its working directory, so age scoring stays correct when the CLI process runs from a different directory than the scanned project.
  • Health time-machine (--history) now refuses to inherit a parent repo's tags. git tag climbs the directory tree, so running the scan on a subdirectory of another repo (the case that surfaces here is the publish script redirecting TMP into build/test_tmp inside this repo) silently reported the parent's tags as the project's own history. The scan now requires the scanned path to host its own .git entry (directory or worktree file) before asking git anything.
  • Publish-time US-English spelling audit now also flags British forms embedded in CamelCase identifiers (e.g. _ScanCancelled, OnColourPicked); the original word-boundary regex missed these because there is no \b between two letters of the same identifier. The audit also exempts archived plan docs under plans/history/ so old write-ups don't keep tripping the gate after their work has shipped.

13.10.4 #

The avoid_money_arithmetic_on_double rule stops mistaking layout math for money — names like trailingTotal or widthTotal that add up pixel widths are no longer flagged, while genuine money totals like totalPrice and invoiceTotal still are. log

Fixed #

  • avoid_money_arithmetic_on_double no longer flags non-financial *Total aggregates — identifiers ending in total (trailingTotal, widthTotal, angleTotal, and single words like cartTotal) are pixel/geometry sums rather than currency, so they are now exempt unless paired with a real money word such as totalPrice or invoiceTotal. No action required; any // ignore: markers added to work around this false positive can be removed.

13.10.3 #

The prefer_spread_over_addall style hint stops nagging when you mutate a collection in place — clearing a list and re-filling it, for example — where spread syntax simply can't apply. The avoid_large_list_copy rule no longer warns on .toList() calls where a concrete list is actually required. The lint score in the status bar no longer flashes a misleading red 0% while a scan is still in progress, and a low score no longer paints the status bar red. The Findings dashboard's health gauge is steadier too — it no longer collapses to an empty dot or whiplashes from A to E while a scan is running. Its Top Rules list now shows the 10 noisiest rules, sorts on a header click, and expands each rule to reveal its full message and the files it affects. And move_variable_closer_to_its_usage now understands that loop accumulators and long initializers genuinely can't move, so it stays quiet on them. log

Fixed #

  • prefer_spread_over_addall no longer flags in-place mutation that has no spread equivalent — it previously fired on every addAll call, including clear(); addAll(items); on the current object (where there is no receiver to spread into) and unrelated user-defined addAll methods. It now reports only addAll on a List/Set/Queue receiver that exists to spread into. No action required; any // ignore: markers added to work around this false positive can be removed.
  • avoid_large_list_copy no longer flags .toList() that is structurally required — a .toList() used as a cascade target (...toList()..sort()), as a branch of a ternary assigned to a typed List, or bounded by take(N) is no longer reported, because a concrete list is unavoidable or the copy is already bounded. No action required.
  • move_variable_closer_to_its_usage no longer flags variables that cannot move closer — it now measures distance in intervening statements instead of source lines and stays silent when the first use is nested inside a loop, branch, or block the declaration must enclose, clearing false positives on loop accumulators, multi-line initializers, and values read across sibling branches. No action required; any // ignore: markers added to work around this can be removed.

Added (Extension) #

  • Each Top Rules row on the Findings dashboard now expands to show the rule's full message and the files it affects, with each file clickable to jump straight to it — triage a noisy rule without scrolling down to the findings list. No action required.

Fixed (Extension) #

  • The status bar no longer shows a false 0% lint score from an in-progress scan — the score divides violations by the files analyzed so far, so a partial editor sweep could crater it; it now appears only once a full analysis has covered enough of the project. If the tooltip says "partial scan", run a full analysis to get the score.
  • The status-bar "updates available" count no longer over-reports — it had counted packages whose update status was undetermined (offline / no pub.dev data) as available updates; it now counts only packages with a real newer version. No action required.
  • The Findings dashboard health gauge no longer collapses to an empty dot — its entrance animation restarted on every refresh and got stuck near the empty frame; the ring now paints the true score instantly on every render. No action required.
  • The Findings dashboard "Group by" dropdown is now legible when open — the option list inherited a low-contrast highlight; it now uses the editor's dropdown colors. No action required.

Changed (Extension) #

  • Saropa now uses a single status-bar item instead of two — the score/vibrancy summary and the separate finding-count badge were merged, so a high score no longer sits next to a contradictory "⚠ N" count; the count is now appended to the one item (e.g. Saropa: 98% ▼2 · V8/10 · ⚠ 96) and clicking it opens the Findings Dashboard. No action required.
  • The lint score no longer colors the status bar red — a low score is informational, not an error, so the status-bar background now stays neutral. No action required.
  • Suppressed packages no longer skew the vibrancy score, "updates available", or problem counts — dismissing a package now removes it from the status-bar numbers and the tree's update/problem badges alike, while the "packages scanned" line shows how many are suppressed so the totals reconcile. No action required.
  • The Findings dashboard health grade no longer whiplashes from A to E mid-scan — it dims to a "computing" state while an analysis is streaming results in, then reveals the settled grade once the run finishes. No action required.
  • The health gauge shows the score without the "/100" suffix — the denominator was redundant next to the letter grade. No action required.
  • The Findings dashboard Top Rules table is trimmed to the 10 noisiest rules and its Rule / Count / Severity headers are now click-to-sort — fewer, richer rows that you can reorder for triage. No action required.
Maintenance
  • Wired the vibrancy-state unit tests into the test build (they had drifted out and stopped compiling) and refreshed their fixtures; added coverage for the shared isUpdatable predicate and suppressed-package exclusion.
  • Added a filesExpected field to violations.json (the project-file denominator) so the extension can detect a partial analysis.
  • Documented the empty cross_file_fixture/test/*_test.dart files as mirror-test presence markers (existence-only fixtures, not stub tests) to stop them reading as broken.

13.10.2 #

This release cleans up the extension's translated interface. The Saropa brand name is no longer turned into local scripts, stray placeholder gibberish has been cleared out of several languages, and a batch of toolbar and status strings that were stuck in English are now translated across all 24 languages. The Help panel also shows the version you actually have installed. log

Fixed (Extension) #

  • The "Saropa" brand name is no longer translated or transliterated in any language — it had been rendered in local scripts (Arabic, Hindi, Bengali, and others) across the localized UI, and now stays "Saropa" everywhere. No action required.
  • Removed leftover translation-marker gibberish from several non-English strings — fragments like q0q had leaked into some translated labels and now no longer appear. No action required.
  • The Help panel title shows the version you actually have installed — it had a fixed version baked into each translation that drifted out of date every release; it is now read live at runtime. No action required.
  • Translated UI strings that were previously stuck in English across all 24 languages — toolbar and menu entries (Export, Filter & focus, Reload from disk, Re-enable disabled rules, severity filter) and the package update/vulnerability counts are now localized. No action required.
Maintenance
  • Rewrote the locale generator's placeholder shield to use an ASCII sentinel with a strict integrity check, brand-term protection, and a self-healing cache, so machine translation can no longer ship transliterated brand names or leftover marker residue; poisoned cache entries re-translate automatically on the next run.
  • The publish pipeline now machine-translates newly added English strings and gates the release on full locale coverage, so untranslated or incomplete UI can no longer ship.

13.10.1 #

Fixed #

  • use_setstate_synchronously no longer flags setState after an if (cond || !mounted) return; guard — the early-exit recognizer now treats either operand of an || disjunction as a valid not-mounted check, matching how it already handles && for positive mounted guards. No action required; any // ignore: markers added to work around this false positive can be removed.
Maintenance
  • Stopped tracking packages/saropa_lints_api/pubspec.lock — it had been committed before the .gitignore rule that ignores sub-package lockfiles, so the index contradicted the stated intent (consumers re-resolve; tracked sub-package locks only create merge churn). Now untracked via git rm --cached.
  • Consolidated the build backlog and planning index into ROADMAP.md and dropped the eight already-shipped platform-config rules from it; internal planning docs only, no packaged behavior change.

13.10.0 #

Findings dashboard cleanup: the duplicate Impact filter row, Group-by-Impact option, and Impact-mix donut are gone — Severity is now the single axis (Impact had mirrored Severity since the 5→3 collapse). The "More" menu is grouped into Export / Filter / Open / System sections with separators, file paths in the findings table truncate from the front so the filename stays visible, the redundant toolbar Refresh has moved into the menu as "Reload from disk", and a new "Re-enable disabled rules…" item lets you recover from an accidental disable without leaving the dashboard. log

Added (Extension) #

  • New More-menu item "Re-enable disabled rules…" opens a multi-select quick-pick over the rules currently disabled in analysis_options_custom.yaml and re-enables the ones you tick — closes a gap where disabling a rule from the Findings dashboard left no in-dashboard path back. No action required.
  • New More-menu item "Reload from disk" in the System section re-renders the dashboard from the existing violations.json without re-running the analyzer; replaces the visible toolbar Refresh button which was indistinguishable from Run analysis. No action required.

Changed (Extension) #

  • Findings dashboard "More" menu is now grouped into Export / Filter & focus / Open dashboard / System with section headers, horizontal separators between groups, and a uniform-width icon column so labels align — the flat 17-item list was visually inscrutable. No action required.
  • File paths in the findings table truncate from the front instead of the end so the filename (the part you most need to read) stays visible when the column narrows; the full path is still in the hover title. No action required.

Removed (Extension) #

  • Duplicate Impact filter UI removed from the Findings dashboard — the second pill row, "Group by Impact" option, Impact-mix donut chart, and impact chips on the active-filter strip all mirrored Severity since the 5→3 collapse on 2026-05-03 and added no information. The underlying Violation.impact / byImpact fields in violations.json are kept for back-compat with external consumers. No action required.
  • Visible toolbar Refresh button removed in favor of "Reload from disk" inside the More menu (see Added). The hidden #btn-refresh stub is retained so existing keybindings and selectors continue to resolve. No action required.

13.9.2 #

Maintenance
  • The pub.dev and VS Code Marketplace listings now share one READMEextension/README.md is gitignored and regenerated from the root README.md at publish time (same pattern already used for extension/CHANGELOG.md), so the package and extension descriptions can no longer drift apart.

13.9.1 #

Fixed #

  • avoid_positioned_outside_stack no longer fires when Positioned is passed via a List<Widget> (or child:) parameter to a custom widget that internally spreads it into a Stack — e.g. FocusCard(backgroundLayers: [Positioned(...)]) where FocusCard is a user-defined card that hosts a Stack inside its own build(). The ancestor walk now treats the direct custom-widget parent as indeterminate (its internal layout is invisible to static analysis); Flutter framework widgets like Column/Row are still walked past, so the real bug Column(children: [Positioned(...)]) continues to lint. No action required — remove any // ignore: avoid_positioned_outside_stack you added for this shape.

Added (Extension) #

  • Package Dashboard now explains the project grade — clicking the radial gauge (or the Project Package Grade summary card) opens a new "Why this grade?" panel showing the score distribution, risk signals (flagged / vulnerable / updates available), the five lowest-scoring packages, and the score-to-grade thresholds. Every row inside the panel is interactive: distribution and signal entries filter the package table, and the lowest-scoring entries jump straight to the relevant row. No action required.

Fixed (Extension) #

  • Package Dashboard now shows code size, not tarball size, across every surface — the Size column, Total Size summary card, package detail panel, package comparison "Code Size" dimension, sidebar detail view, and Size Distribution chart all now read what each package contributes to your built app (its lib/ plus declared assets), with the gzipped archive total as a labeled fallback when the analyzer hasn't run yet. The Package Dashboard's Health Score panel also gains +example / +tests / +tools / +docs rows when a package ships those folders, so the bonus already feeding the overall score is now visible row-by-row instead of invisible. Previously the v13.9.0 fix only landed in the editor hover; every dashboard surface still showed the old tarball number (e.g. audioplayers rendering as 20,535 KB on the Dashboard table). No action required.
  • Package Dashboard size tooltips now describe code size in every language — the Size column header, Total Size caveat, and Footprint toggle tooltips said "archive size before tree shaking" in all 25 shipped locales, contradicting the corrected number shown beside them. The copy now explains the column reports code size (lib + declared assets) and falls back to archive size only when code size is unavailable. No action required.
  • Package Dashboard radial grade gauge now paints the arc again — under the strict webview CSP the inline CSS variables that drove the stroke length were being dropped, so only a single rounded line-cap dot was visible next to the letter grade. The arc and its load animation now use SVG presentation attributes and SMIL, which survive the CSP. No action required.
  • Package Vibrancy toolbar no longer shows a redundant "Search packages" label next to the search box — the label is now hidden from view (it remains for screen readers) so the placeholder text inside the input is the only visible cue. No action required.
  • Package Vibrancy toolbar buttons read as buttons in every theme — Rescan / Open Project / Copy / Save / pubspec.yaml had a full-pill shape and a transparent border fallback that disappeared on themes that don't define button.border. Buttons now use a softer rounded-rect (6px) and fall back to widget.border, matching the FOOTPRINT segmented control which moved off the full-pill shape for the same reason. No action required.
  • "All" age-slider label no longer reads as the value of the Preset dropdown — the divider between the Published-age group and the Preset group is now higher-contrast, the trailing gap is wider, and the slider's max-value readout sits in a small chip so it stops blending into the neighboring "Preset" label. No action required.
Maintenance
  • Added dedicated tests pinning the code-size dashboard behaviors (size cell prefers codeSizeBytes, archive fallback, on-disk tooltip asymmetry, Health Score maintainer-quality rows, codeSize JSON export field, "Code size" column tooltip copy).
  • Added comparison-ranker.test.ts to the test tsconfig include list — the file existed but never compiled, so its stale "Archive Size" dimension assertion went unrun.

13.9.0 #

The extension UI is now fully translated into every shipped non-English language — sidebar, dashboards, status bar, command palette, and webviews no longer fall back to English. Package Vibrancy stops over-flagging packages that ship demo media or sample servers, drops misleading "replace with itself" upgrade hints, and rewards packages that include example, test, and doc folders. The Package Dashboard also gains collapsible sections, attaches tooltips to the cards they describe, hides toolbar buttons when there's nothing to do, and renders the Dependency Network diagram cleanly instead of as overlapping garbled text. log

Added (Extension) #

  • Extension UI now fully localized across all 24 shipped non-English locales — every string in the sidebar, dashboards, status bar, command palette, and webviews now has a curated translation for ar, bn, de, es, fa, fil, fr, he, hi, id, it, ja, ko, nl, pl, pt, ru, sw, th, tr, uk, ur, vi, and zh. Previously many UI fragments fell back to English in non-English locales because the dictionary was sparse and a defensive guard in the translator was silently reverting legitimate translations whose placeholders sat at non-leading positions. No action required.
  • Collapsible Package Dashboard sections — Size Distribution, Filters, and the Packages table each sit inside their own expander now so you can fold any of them away to focus on the rest of the view; all three default to open so the landing experience is unchanged. No action required.
  • Package hover now reports code size and credits maintainer-quality folders — the primary "size" line shows what a package actually contributes to your built app (its lib/ plus assets it declares for bundling), not the pub.dev tarball total. The hover separately shows the on-disk total with a per-folder breakdown so you can see when a tarball is dominated by demos or test fixtures, and packages that ship example/, test/, tool/, or doc/ now earn positive health-score components instead of bloat penalties. No action required.

Fixed (Extension) #

  • Vibrancy bloat rating no longer over-reports packages that ship sample media or demo servers — bloat now scores on code size (what reaches your app), not the gzipped tarball, so packages like audioplayers (which carry tens of MB of demo audio in example/) drop from a 9/10 bloat alarm to a low rating that matches their actual install cost. The per-app Total Size budget and the comparison view's size dimension switched to the same measure. No action required.

  • Self-replacement entries removed from the curated package issues list — 30 known-issue entries (audioplayers, file_picker, flutter_local_notifications, flutter_typeahead, and others) listed themselves as the replacement, which produced misleading "Replace with X" UX where X was the same package the user already had. Those entries still flag the affected old versions, but the upgrade path comes from the version range rather than a self-pointing replacement. No action required.

  • Package Dashboard toolbar buttons hide when their action is a no-op — the "← Back" package-navigation button, the "× Clear" chart filter indicator, and the "↻ Reset view" toolbar button now stay hidden whenever there's nothing to act on (no nav history, no live chart filter, view state matches defaults). Back was previously shown disabled, Reset view was always shown, and the chart Clear strip could survive a session restore that referenced a package no longer in the chart. No action required.

  • Size Distribution bars render at their correct width and color again — after the chart was wrapped in a <details> expander, the bar-fill elements rendered at 0% width with no visible color because the inline --bar-width custom property failed to apply. The renderer now emits the style on a single attribute line (matching the Findings Dashboard's working pattern) and the chart script re-applies the width via setProperty() at init from a duplicate data-bar-width attribute, so the bars survive whichever rendering path the webview takes. No action required.

  • Package Dashboard caveats now attach to the cards they describe — the tree-shaking footnote ("Archive sizes before tree shaking…") now appears as a tooltip on the Total Size card, and the activity-threshold legend ("90d = stale, 180d = dormant") now appears in each grade card's tooltip (Vibrant/Stable/Outdated/Abandoned/EOL). Both previously sat in a floating note at the bottom of the summary block where readers couldn't easily connect them to the relevant data. No action required.

  • Package dashboard Dependency Network panel rendered as garbled overlapping text — the diagram now lists each transitive once on the right column with edges fanning in from every direct that pulls it in, instead of duplicating shared transitive labels at colliding Y positions. The panel also moved below the package table so it no longer pushes the table off-screen. No action required.

Maintenance
  • Publish script now resolves dependencies in every workspace package before any analyze runs. A new "Dependencies" step in scripts/publish.py runs dart pub get in the project root and in every packages/*/ with a pubspec.yaml, ahead of the audit, format, analyze, and test gates. Previously a stale or missing .dart_tool/package_config.json in packages/saropa_lints_api/ surfaced as thousands of phantom package:test/test.dart errors during the audit's analyze step, which forced a manual abort + two-directory dart pub get + restart of the whole pipeline.
  • Translation pipeline (extension/scripts/i18n/) modernized. generate_translations.py now prints colored per-locale progress (with Windows VT enabling), labels the prefetch step with an explicit "translating N new strings via Google…" count, persists the MT cache after every locale so a Ctrl-C never throws away paid-for Google calls, and exits cleanly (130) instead of dumping a Python traceback. A final coverage audit writes extension/reports/i18n_translation_audit.md with a cross-locale rollup (most-missed strings first), paste-ready Python dict stubs per locale, and a per-locale missing list. The translator's placeholder-rename guard now compares placeholders as a set (Bengali, Japanese, Korean legitimately reorder {count}/{ruleCount}) and its MT-garbage "leading-garbled" guard is skipped for curated dictionary entries (Arabic, Ukrainian, Turkish, etc. legitimately put modifier words before the first placeholder). Curated "X": "X" passthrough entries in dictionaries.py are now counted as translated rather than missing in the audit.

13.8.0 #

The Findings Dashboard now lets you reconcile its count with the Problems panel without leaving the window: clickable pills in the hero status line surface analyzer findings (built-in Dart SDK lints plus any third-party custom_lint plugins like riverpod_lint) and analyzer-side TODOs that fall outside saropa's rule set, plus a discoverability prompt for the existing TODO/HACK workspace scanner. All three default off; one click toggles each on or off, and none feed health score, KPI cards, or filtering. log

Added (Extension) #

  • Findings Dashboard supplementary pills (#224) — three clickable pills in the dashboard hero surface non-saropa analyzer findings, analyzer-side TODO diagnostics, and the existing TODO/HACK scanner toggle directly on the surface that has the discoverability gap. New workspace settings saropaLints.includeOtherAnalyzerFindingsInDashboard and saropaLints.includeAnalyzerTodosInDashboard (default off); commands Saropa Lints: Toggle Show Other Analyzer Findings on Dashboard, ... Toggle Show Analyzer TODOs on Dashboard, and ... Toggle TODO/HACK Workspace Scanner invokable from the command palette. No action required.

13.7.2 #

Added (Extension) #

  • Auto-analyze on dependency changes — the extension now watches pubspec.lock and automatically re-runs dart analyze when dependencies change (after pub get / pub upgrade), with a 10-second cancel-restart debounce to coalesce rapid lock-file rewrites. Controlled by the new saropaLints.runAnalysisAfterDependencyChange setting (default: on); toggle from the sidebar Settings row or command palette. No action required.
Maintenance
  • Tracked reports/organize_reports.py in git by switching .gitignore from directory-level to content-level ignore with a negation rule; also added example*/reports/ to .gitignore so generated report output under example directories stays untracked.

13.7.1 #

Fixed #

  • avoid_string_substring no longer fires on indexOf-guarded, loop-bounded, or early-exit-guarded substring calls — the rule now recognizes while/for loop conditions, preceding if (idx == -1) return guards, and if-conditions that reference substring arguments as evidence of bounds safety. No action required.
  • Analyzer v9 useDeclaringConstructorsAst crashes resolved — all .namePart.typeName accesses (132 sites) and .namePart.typeParameters accesses (4 sites) now use safe nameToken / nameTypeParameters extensions that fall back to the pre-gate .name / .typeParameters API; additionally, _wrapCallback now catches UnsupportedError globally so any remaining gated property on any analyzer version skips the rule gracefully instead of crashing the plugin. Closes the remaining failures reported in #224. No action required.

Fixed (Extension) #

  • Regression-nudge toasts no longer stack during slow linting — when the analyzer writes partial results over several seconds the score can cross multiple thresholds downward, previously firing a separate notification for each; now the nudge debounces for 3 seconds and shows only the worst threshold crossed. No action required.
Maintenance
  • Ran dart pub get in packages/saropa_lints_api/ to resolve missing test dependency; added source comment noting sub-package requires its own dependency resolution.

13.7.0 #

The VS Code extension dashboard is now fully internationalized, and two analyzer-facing bugs are fixed — a false positive on Face ID rules when the plist key was already present, and a crash on projects running analyzer v9.

Changed (Extension) #

  • Dashboard i18n: remaining webview strings routed through runtime keys — Code Health, Config Dashboard suppressions strip, Lints Config mirrors, Related Rule Telemetry, sidebar layout panel, and Security Posture tree now resolve all user-facing text through l10n() instead of hardcoded English literals; locale files regenerated for all 24 shipped locales. No action required.
  • Language picker: reload prompt and multilingual discoverability — changing the UI language now prompts to reload the window (manifest NLS labels like sidebar and command titles require a VS Code reload to take effect); the command palette entry shows the word "Language" in five languages so non-English speakers can find it; the "Auto" option shows which locale it resolves to. No action required.

Fixed #

  • Analyzer v9 useDeclaringConstructorsAst crash — all 335+ .body.members call sites now use safe bodyMembers / bodyConstants extensions that fall back to the pre-declaring-constructors API when the gate throws; projects on analysis_server_plugin ^0.3.4 with analyzer 9.x can run dart analyze without the plugin crashing. No action required.
  • require_ios_face_id_usage_description false positive when Info.plist key is present — the rule's early-return guard failed to locate the project root when the analyzed file was resolved via a non-filesystem URI scheme (package:, dart:, etc.), causing the guard to fall through and fire on every LocalAuthentication call site even when NSFaceIDUsageDescription was already configured; URI handling and Windows path normalization in InfoPlistChecker are now robust. No action required.
Maintenance - **Runtime i18n function renamed `t()` → `l10n()`** — the translation lookup function in `runtime.ts` is now `l10n()` for clarity; all 492 call sites updated. No action required unless you import `t` from `src/i18n/runtime` in a fork.

13.6.0 #

This release improves how you steer large findings lists and repeat searches across sessions (multi-key sort, bulk JSON copy, and workspace-persisted recents). It tightens RTL behavior for stack toggles, adds cross-file CLI defaults you can commit in analysis_options.yaml, gives IDE troubleshooting clearer native-plugin-first guidance, and ships a verify-nls-keys check for Marketplace manifest parity. Score and dashboard consistency improvements from earlier work remain in place. log

Changed (Extension) #

  • Findings Dashboard consistency and run UX — score/toast regression nudges now use the same visible-findings basis as the dashboard (ending stale-history vs dashboard-empty mismatches), and the dashboard now shows an in-panel progress bar with duplicate-click guard plus an automatic full refresh after analysis completes; no action required.
  • Findings triage ergonomics — you can Shift+click a second column header as a tie-breaker (labeled ① / ②), checkbox-select multiple findings, and copy the selection as JSON; large tables defer off-screen row work via content-visibility so scrolling stays smoother; recent text-filter queries now persist per workspace across reloads alongside the Known Issues catalog and Command Catalog search popovers; no action unless you routinely clear Workspace State intentionally.
  • Lints Config pack toggles RTL — the enable switch knob now tracks right-to-left layouts using logical positioning so the knob lands on the expected side; reload the dashboard if you switched UI direction mid-session; no YAML change needed.
  • Manifest localization guardnpm run verify-nls-keys (under extension/) asserts every %…% placeholder from extension/package.json resolves in extension/package.nls.json, catching missing English strings before Marketplace packaging; contributors should run it after editing contributed labels or command titles.
  • More shipped UI locales filledpackage.nls.* and src/i18n/locales/* now include machine-translated copy for bn, fa, fil, he, id, pl, sw, th, tr, uk, and vi (plus corrections for de, nl, and ru dependency-count wording); regenerate with SAROPA_I18N_MACHINE_TRANSLATE=1 if you fork strings. Hebrew uses Google target code iw in the generator.
  • Package Vibrancy freshness vs tree — new-version toasts patch the last scan so the tree and dashboard match the toast, rewrite the workspace startup fingerprint with that snapshot so VS Code reloads before the next full scan still show those versions, and reuse the last dependency graph for lightweight republish; no action required beyond normal Package Vibrancy use.

Changed #

  • Cross-file CLI project defaultsdart run saropa_lints:cross_file … merges optional saropa_lints_cross_file settings from analysis_options.yaml (shared excludes and heuristic/analysis toggles), with explicit CLI flags still applied on top; add that map once per repo if you reuse the same exclusions between engineers and CI; no Dart source change beyond refreshing config when adopting it.
  • Additional quick fixesavoid_redundant_semantics, require_baseline_text_baseline, and avoid_unconstrained_dialog_column now offer IDE fixes (remove redundant Semantics around an Image that already has semanticLabel, insert textBaseline when using baseline cross-axis alignment, and add mainAxisSize: MainAxisSize.min for Column inside dialogs); apply only where the suggestion matches intent; no config change.
Maintenance
  • Contributor planning docs — assorted plans/*.md checklists (quick-fix, testing/release, stub tests, localization guide, severity follow-ups, cross-file CLI, UX backlog, rule-pack migration, comment coverage) were refreshed for clearer sequencing and sign-off trails; docs only, no shipped rule or extension behavior beyond what is listed under ### Changed (Extension) above.
  • Maintainer tooling — the quick-fix audit script prints a stdout summary before writing the dated report file, and the bundled quality-gate example YAML documents recommended new_* metrics with legacy-alias notes; no action unless you vendor that sample into your own CI gate.
  • Comment coverage CLI — the worst-files table uses an L/C column (physical lines per comment line, higher means sparser) instead of a percentage so maintainers spot thin files faster when running the comment-coverage script locally.

13.5.0 #

This release restores real compatibility for analyzer 9 consumers, including Flutter-stable setups that cannot move to analyzer 12+ yet. The plugin and CLI now build cleanly instead of failing during startup due to missing analyzer APIs, so projects pinned to analyzer 9 can run dart analyze again with current saropa_lints. log

Fixed #

  • Analyzer 9 compatibility — analyzer-version shims now backfill API gaps (lowerCaseName, constructor/extension-type accessors, and registry deltas) so saropa_lints compiles and runs on analyzer 9 instead of crashing during plugin bootstrap; no action required beyond upgrading.
  • Rule AST compatibility — extension/constructor rule paths now handle analyzer-9 node shapes (nullable extension bodies, primary-constructor availability, and member traversal) so previously failing rule files compile and execute consistently; no action required beyond upgrading.
  • CLI bootstrap on older analyzers — CLI entrypoint shebang handling was corrected so command wrappers no longer fail parsing before imports on analyzer-9 toolchains; no action required beyond upgrading.

13.4.9 #

This release smooths language and notification behavior in the extension. UI language selection now saves reliably, and score-milestone feedback is quieter during cleanup work. Translation strings for the picker are now complete across shipped locales, so language switching feels consistent. log

Fixed (Extension) #

  • Pick UI language — choosing a language no longer fails with "Unable to write to Workspace Settings … not a registered configuration"; the picker now saves saropaLints.uiLanguage to User settings so the choice applies reliably across workspaces. No action required — pick your language again if a previous attempt failed.

Changed (Extension) #

  • Health score milestones — crossing an upward band (50–90) now shows a short status-bar message instead of an information notification, so steady improvement does not spam the notification center. No action required.
  • Pick UI language — the quick pick and sidebar UI language row list each language in native script with the English name in parentheses (except English), so the list is readable in the language itself and still easy to cross-reference for maintainers. No action required.
  • Runtime locale catalogsuiLanguage.pick.* strings (quick pick title, placeholder, and Auto row) are translated in every shipped extension/src/i18n/locales/<locale>.json, with matching phrase keys in extension/scripts/i18n/dictionaries.py so generate_locales.py keeps them after regeneration. No action required.
  • UI language scope (docs)extension/scripts/i18n/README.md now states explicitly that User settings are the intended scope so one language applies across all workspaces; per-workspace overrides remain out of scope unless product requirements change. No action required.
Maintenance
  • Publish workflowscripts/publish.py / extension packaging: US English spelling check skips extension/scripts/i18n/ (translation data, not US-maintained prose); optional regeneration of package.nls.<locale>.json and runtime locale JSON from English sources before compiling the VSIX. No action required for package consumers.

13.4.8 #

This release adds first-class localization support to the extension UI and introduces a direct language picker workflow. It also fixes a lingering package-size chart issue so visual weighting matches real values. If you use translated UI strings or the vibrancy chart, this is the reliability pass for both. log

Added (Extension) #

  • Localization framework — contribution strings in package.json now resolve through package.nls.json, with generated package.nls.<locale>.json files for additional languages. Shared webview strings live under extension/src/i18n/ with a runtime locale picked from VS Code's display language or saropaLints.uiLanguage.
  • Pick UI language — new command and prominent Actions row to switch language; open dashboards refresh automatically so you can verify translations without reloading the window.

Fixed (Extension) #

  • Size Distribution chart now actually renders each bar at a length proportional to its share of total size. The fix shipped in v13.4.6 didn't take effect in the live webview and v13.4.7 didn't carry a re-fix, so bars kept rendering at the full track width across both releases; this release switches to the same CSS pattern the Findings Dashboard's bar charts have used reliably for months. No action required — reopen the report after updating.

13.4.7 #

Package Vibrancy no longer launches a fresh scan after every individual pub upgrade. The watcher now waits for a whole upgrade session to settle, runs at most one scan at a time, and skips when pubspec.lock has not actually changed — the overlapping toasts and slowdown when upgrading several packages in a row are gone. The Package Dashboard webview also stops looking like a "dead page" during the very first scan: instead of the empty-state grade-E gauge with zero rows it now shows a clear "Scan in progress" placeholder, and the open panel auto-refreshes when the scan finishes.

Changed (Extension) #

  • Package Vibrancy — the file-system watcher now debounces pubspec.lock changes by 30s (was 5s) so a session of back-to-back pub upgrade calls collapses into a single trailing scan instead of starting a fresh ~60s scan after each individual upgrade. Previously the abort-on-supersede pattern still left earlier scans running to completion (their HTTP fetches don't honor the abort signal), so three sequential upgrades produced three overlapping toasts and heavy CPU/network contention. No action required.
  • Package VibrancyrunScan now coalesces concurrent invocations: if a scan is already in flight, a second call stashes its options and the in-flight scan launches exactly one trailing scan when it finishes. Callers no longer stack parallel scans, and forceRefresh: true is sticky across coalesced calls so a "Rescan (clear cache)" click is honored even when it lands during a watcher-triggered scan. No action required.
  • Package Vibrancy — the watcher now hashes pubspec.lock against the persisted last-scan fingerprint and skips when bytes are unchanged. pub get against an unchanged tree, git operations that restore the same lock, and IDE auto-resolve no longer trigger a wasteful full rescan. No action required.

Fixed (Extension) #

  • Package Dashboard — the webview now shows an explicit "Scan in progress" placeholder when opened during the first scan instead of the empty dashboard with Grade E · 0/100, an empty radial gauge, and an empty table. Users were reading the empty-state render as a broken or failed scan. No action required — open the dashboard while a fresh scan is running to see the new placeholder.
  • Package Dashboard — the open dashboard panel now auto-refreshes when a scan completes. It used to be built once from latestResults and never re-render itself, so users who opened it during a scan stayed on stale or empty data until they manually reran the "Show Report" command. No action required.

13.4.6 #

This release fixes two high-friction issues users hit during normal analysis work: the Package Vibrancy size chart now renders proportionally, and tier YAML version pinning no longer breaks analyzer-plugin resolution on newer analyzer stacks. In practice, charts are readable again and analysis setup is less likely to fail after upgrades. log

Fixed (Extension) #

  • The Size Distribution chart in the Package Vibrancy report now renders each bar at a length proportional to its share of total size. The earlier attempt to fix this didn't take effect in the VS Code webview, so bars kept rendering at the full track width; bars now use the same width-and-grow pattern the Findings Dashboard charts already use reliably. No action required — reopen the report after updating.

Fixed #

  • lib/tiers/{essential,recommended,professional,comprehensive,pedantic}.yaml no longer pin the embedded plugin to the old ^5.0.0-beta.8 constraint that's been frozen since Feb 2026. The Dart analyzer's plugin manager fetches that constraint into a synthetic project under .dartServer/.plugin_manager/<hash>/ and runs pub upgrade against it — with the stale pin, anyone whose project also depends on a package requiring a newer analyzer (e.g. riverpod_lint ^3.1.3 requiring analyzer ^9.0.0) had dart analyze abort with "An error occurred while setting up the analyzer plugin package". The yamls now ship ^13.0.0, which resolves cleanly against the current analyzer range, and the publish script keeps them in sync with pubspec.yaml on every major bump so they can't drift again. Reported as #216. No action required after upgrading; consumers using include: package:saropa_lints/tiers/<tier>.yaml will start resolving cleanly on the next pub get.
Maintenance
  • New module scripts/modules/_tier_yaml_version.py rewrites the saropa_lints version: line in each lib/tiers/*.yaml at publish time, anchored to the current major from pubspec.yaml. Runs inside the existing "Version sync" step so the change ships in the same publish commit as the version bump. 13 unit tests pin the contract — major-only widening, idempotent re-runs, CRLF preservation, and no false-matches against unrelated version: keys.

13.4.5 #

This release reduces false positives in command-line and tooling code paths. Rules that make sense for Flutter UI threads no longer fire on scripts under tool/, so local utility scripts and generators stop producing noisy warnings. It keeps lint signal focused on code where the risk model actually applies. log

Fixed #

  • avoid_blocking_main_thread (and other UI-thread rules) no longer fire on scripts under tool/ — repo-local CLI utilities run via dart run and never execute on a Flutter UI isolate, so sync I/O is legitimate there. Mirrors the existing skip for bin/. No action required.
Maintenance
  • tool/rule_pack_audit.dart and tool/generate_rule_pack_registry.dartapplyCompositeRulePacks now returns a new map instead of mutating its argument, clearing the avoid_parameter_mutation lint. Both call sites updated to consume the returned map. No change to extracted pack contents or generator output.
  • Plugin self-source analysis_options.yaml excludes tool/** belt-and-braces, matching the existing bin/** exclusion. The cached plugin snapshot lags local edits to SaropaContext.isCliToolScript, so the host-level exclude prevents dart analyze noise during the cache rebuild window.

13.4.4 #

This release expands quick-fix coverage and hardens extension update-check behavior. More rules now have one-step IDE fixes, and upgrade notifications break through stale dismiss windows when a new version is actually available. For day-to-day users, that means faster cleanup and fewer “why didn’t I get prompted?” moments. log

Added #

  • Quick fix coverage extended to ten more rules (Batch 13). Each rule now offers a one-step IDE correction so the lint can be cleared without manual edits — no action required if you already had these rules enabled. The newly-fixable rules are prefer_raw_strings, prefer_period_after_doc, format_comment_style, prefer_const_border_radius, prefer_const_widgets_in_lists, avoid_redundant_async_on_load, avoid_single_cascade_in_expression_statements, avoid_escaping_inner_quotes, avoid_types_on_closure_parameters, and prefer_expression_body_getters.

Fixed (Extension) #

  • Upgrade-check throttle now lets a newly-published saropa_lints version break through the dismiss memory immediately instead of being suppressed for up to 24 hours. The previous gate was a single 24h timer, so a release published the morning after a dismiss stayed invisible until that timer elapsed; the gate is now a 1-hour anti-thrash window combined with a per-version dismiss memory, so a new pub.dev version always re-prompts even within the same day. Legacy state self-heals on the next write — no user action required.
Maintenance
  • Removed two orphan extension commands — saropaLints.config.copyAsJson ("Copy Triage as JSON") and saropaLints.overview.copyAsJson ("Copy Overview as JSON") — that were declared in package.json and listed in the command catalog but had no runtime handler after the Triage and Overview trees were merged into Settings/dashboards. Invoking them from the palette previously failed with command not found; the entries are now gone.
  • Version 13.4.2 was bumped in pubspec.yaml but never tagged or published — the v13.4.3 publish run jumped past it. No 13.4.2 artifact exists on pub.dev or the Marketplace; consumers go directly from 13.4.1 to 13.4.3.
  • scripts/modules/_version_changelog.py now refuses to publish when any ## [X.Y.Z] section in CHANGELOG.md has an empty body — that was the exact shape that caused the rename-collision recovery in apply_version_and_rename_unreleased to silently skip 13.4.2 and bump straight to 13.4.3. Authors must now either delete the orphan stub or fill in its release notes before re-running publish.
  • scripts/modules/_rule_metrics.py now finds nested rule tests under test/rules/{group}/, fixing the gap report that falsely listed widget_patterns_avoid_prefer, structure, async, bloc, and performance as missing. The previous flat test/*_test.dart glob saw zero rule-category tests; coverage is now reported correctly (116/116 categories tested, 1095 test calls).
  • scripts/modules/_extract_rule_messages.py now extracts all 2165 rules instead of producing an empty JSON dump. Two bugs landed when the script was moved into scripts/modules/: the flat glob("*_rules.dart") only matched the barrel export (zero LintCodes), and the parent.parent walk pointed at scripts/lib/src/rules/ — a non-existent path — so even fixing the glob alone would have returned zero. The CLI body is now guarded by if __name__ == "__main__": so importing the module no longer mkdirs reports/ or writes a JSON file as a side effect.
  • Moved release notes for 12.5.2 through 12.6.1 from CHANGELOG.md to CHANGELOG_ARCHIVE.md so the active changelog stays focused on the current 13.x series. No action required for package users.
  • Added file-level doc headers and per-test WHY comments to four low-coverage test files surfaced by the publish-time comment-coverage scan: test/integrity/plan_additional_rules_21_30_test.dart, test/rules/architecture/compile_time_syntax_rules_test.dart, test/rules/core/performance_rules_test.dart, and extension/src/test/vibrancy/services/dep-graph.test.ts. Headers explain the contract under test (registration + tier + fixture invariants for the rule packs; two-output shape for the dep-graph parser) so a future reader can change a rule without first decoding what each it() was guarding. No action required — no source, severity, tier, message, or fix changed.

13.4.3 #

Brings the Findings Dashboard back for projects whose report file was last produced by an older saropa_lints plugin — counts and the findings table agree again, no re-analysis needed. The "no analysis report" notice is also clearer: it spells out which piece of project setup is actually missing (pubspec, dev-dependency, analyzer config, or a top-level saropa_lints: key that doesn't enrol the plugin) and offers a one-click Set Up Project action for the common cases. log

Fixed (Extension) #

  • Findings Dashboard no longer reads "401 findings" with an empty findings table when reports/.saropa_lints/violations.json was written by an older saropa_lints plugin (any version <13.4.x with the legacy critical/high/medium/low/opinionated impact vocabulary). The reader now normalizes those values to the current error/warning/info buckets so the impact filter matches them. No action required after upgrading. Reported as a follow-up to #208.
  • The "no analysis report" notification now classifies the cause precisely — missing pubspec.yaml, missing saropa_lints dev-dependency, missing analysis_options.yaml, malformed YAML in analysis_options.yaml, or a bare top-level saropa_lints: key that doesn't enrol the plugin — and surfaces a one-click Set Up Project button as a modal that explicitly states configuration is required and the dashboard cannot show findings without it. The bare-key case (the issue #208 reporter's exact state) shows the valid include: package:saropa_lints/tiers/recommended.yaml line inline so users can hand-fix without losing custom analyzer settings. The "no pubspec.yaml" and "exclude list too aggressive" cases stay non-modal — those need user judgment and Set Up Project would either be premature or clobber their customizations.

13.4.2 and Earlier #

Looking for older changes? See CHANGELOG_ARCHIVE.md for versions 0.1.0 through 12.6.1.

6
likes
110
points
5.18k
downloads

Documentation

API reference

Publisher

verified publishersaropa.com

Weekly Downloads

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

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

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

License

MIT (license)

Dependencies

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

More

Packages that depend on saropa_lints