saropa_lints 13.11.9
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.
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
13.11.8 #
Fixed #
avoid_nullable_interpolationno longer fires on${x ?? fallback}, on syntacticif (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 insidedebug()strings where seeingnullIS the point). Remove any project-local// ignore: avoid_nullable_interpolationcomments added for these three patterns.
13.11.7 #
Fixed #
avoid_expensive_buildno longer fires on iteration primitives (sort,where,map,fold,reduce) insidebuild(). 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 thereadAsXxxfile-I/O family. Remove any project-local// ignore: avoid_expensive_buildcomments added for the iteration patterns above.
13.11.6 #
Fixed #
avoid_large_list_copyno longer fires when the.toList()is structurally required by a named argument, a??fallback, or a getter on the result. Named arguments likechildren: items.map(...).toList(), null-coalescing chains likesource?.where(...).toList() ?? <T>[], and getter access likeitems.map(...).toList().nonEmptyall need a concreteListand have no lazy alternative — remove any// ignore: avoid_large_list_copyadded for these three patterns.avoid_listview_without_item_extentno longer fires onListView.separated. The.separatedconstructor does not declareitemExtent,prototypeItem, oritemExtentBuilder(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 targetsListView.builderonly — remove any// ignore: avoid_listview_without_item_extentadded on.separatedcall sites.avoid_listview_without_item_extentno longer fires on inline non-scrollingListView.builder. When the call sets bothshrinkWrap: trueandphysics: NeverScrollableScrollPhysics()the inner list does not scroll andshrinkWrapalready 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_extentthat 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 FILEin the first 1KB) catches codegen output that lands at non-standard paths. The hero band shows anN suppressedpill so suppression is visible, never silent. // ignore_for_file: code_healthdirective support. Add// ignore_for_file: code_healthat the top of a Dart file to keep it out of the Code Health scan entirely; add// ignore_for_file: code_health:complex,undocumentedto drop only specific flags from every row in that file. The directive composes with normal analyzer ignore lists (// ignore_for_file: avoid_print, code_healthis 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 readscomplex (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.pyand rerun the generator — happens in-place without restarting the whole publish.
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) insidedependencies:ordependency_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 savedpubspec_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, andlintswas 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 usesaropa_lintsas 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_widgetno longer fires when error handling lives in an extension method onAsyncSnapshot. Centralized helpers likesnapshot.snapLoadingProgress()(returns the loading widget or null) andsnapshot.reportErrorIfAny()were reported as missing error handling because the substring check at the call site never saw the literalhasError/.errortext — the inspection happened inside the extension. The rule now walks the builder body and treats an inline.hasError/.error/.stackTraceaccess, 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 namedhasErrorStatesuppressed the lint via raw substring match. Remove any project-local// ignore: require_error_widgetcomments added to silence the false positive.require_late_initialization_in_init_stateno longer fires on reassignment insideonPressed,onTap, orsetStatecallbacks. Standard "View All / load more" patterns — alatefield that is correctly initialized ininitStateand 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 thatinitStatealready assigns. Remove any project-local// ignore: require_late_initialization_in_init_statecomments added to silence the false positive.pass_existing_future_to_future_builderno longer fires on the cache-method pattern. When thefuture:argument is a private instance method (_getContactsFuture(...)) on a class that declares at least oneFuture<...>?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 alate finalfield 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_buildercomments added to silence the false positive.
13.11.3 #
Fixed #
function_always_returns_nullno longer fires on@overridedeclarations that return null. Overriding a nullable parent member (e.g.@override Color? get barrierColor => null;on a no-barrierModalRoute) honors the parent's contract — the override cannot widen the return type, and returning anything other thannullwould 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_targetsno longer false-fires on wide-band overlays. ASizedBoxorContainerwhose single small axis (e.g.height: 38) wrapped aGestureDetector,InkWell, orInkResponse— typically a dismiss-on-tap pill, list row, orPositioned.filloverlay — 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_targetscomments added to silence the false positive.prefer_layout_builder_for_constraintsno longer fires insidestaticutility methods that take aBuildContext. Static helpers likeMenuUtils.popupMenuConstraints(BuildContext)compute absolute viewport-fraction dimensions for non-widget return types (BoxConstraints,Size,EdgeInsets);LayoutBuilderis structurally inapplicable to them because there is no parent constraint to consult and the return is data, not a widget. Instance methods that takeBuildContext(the 2026-04-28 case) still fire. Remove any project-local// ignore: prefer_layout_builder_for_constraintscomments 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 itsparents[2]repo-root index, leaving CI's test job red against every release commit since (includingRelease v13.11.2). No user impact. - Removed post-publish auto-bump of
pubspec.yaml. Releases no longer auto-commitchore: bump version to n.n.n+1. The next publish prompt now defaults to a patch bump only whenCHANGELOG.mdhas 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.nameacross 25 locale bundles. The static value is now the localized word "Help" alone (no version suffix); the runtimecreateTreeView().titleinjection inextension/src/extension.tscontinues to display the live(vX.Y.Z)frompackage.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 Latinv, 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 inreports/.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 areports/.saropa_lints/health/files.ndjsonshard 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.--complexityadds 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).--deadweightflags unused files and dead top-level symbols (composing the existing cross-file engine);--coverage/--lcovreads line coverage;--gitadds per-file churn, recency, and a bus-factor proxy from history (bounded for large repos).--assetsflags pubspec assets/fonts declared but never referenced in code (heuristic — report-only, verify before deleting).--islandsfinds transitive dead private islands — private declarations that reference each other but are unreachable from any live root, the case Dart'sunused_elementmisses.--couplingsurfaces change-coupled file pairs (files that keep changing together in git history), and--stubsflags tests with no assertions.--fix(with--deadweight) writes a reviewablegit rmscript — 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 markdownemits 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 promptsemits 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.infopredates the latest commit — stale coverage is reported as unverified instead of silently trusted. - Optional
.saropa_health.yamlconfig: an allowlist that silences known false positives in the heuristic sections (dead files, dead symbols, islands, assets) plus sharedexcludeglobs — 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 (viagit 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".
--cycleslists import cycles with a suggested cut per cycle (the stable→volatile edge to remove);--cachereuses 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-visiblering — all gated byprefers-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 htmlwrites 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
awaitoutside 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 yourlib/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-l10napp_localizations*.dartat 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_onlyno longer flags prose mentions of thehttp://scheme — user-facing strings that name supported schemes (e.g.'http:// or https:// URLs are supported.', the Korean equivalent'http:// 또는 https:// URL만 지원됩니다.'in a Flutterapp_localizations_*.dartfile, 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 afterhttp://(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_lintsis 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.jsonand 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 TOTALtruthfully. 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.jsonunder 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 problemschip (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. complexis 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.
unusedANDcomplex), then click Copy filtered (N) to send every visible row to the clipboard asfile: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.yamlunder the pub cache (Pub/Cache/hosted/…,~/.pub-cache/…) or under.dart_tool/node_moduleswas producingsaropa-pubspec,saropa-sdk, andPackage Vibrancydiagnostics 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.enabledtofalseto 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_lintsCLI 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 blamein 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 tagclimbs the directory tree, so running the scan on a subdirectory of another repo (the case that surfaces here is the publish script redirectingTMPintobuild/test_tmpinside 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.gitentry (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\bbetween two letters of the same identifier. The audit also exempts archived plan docs underplans/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_doubleno longer flags non-financial*Totalaggregates — identifiers ending intotal(trailingTotal,widthTotal,angleTotal, and single words likecartTotal) are pixel/geometry sums rather than currency, so they are now exempt unless paired with a real money word such astotalPriceorinvoiceTotal. 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_addallno longer flags in-place mutation that has no spread equivalent — it previously fired on everyaddAllcall, includingclear(); addAll(items);on the current object (where there is no receiver to spread into) and unrelated user-definedaddAllmethods. It now reports onlyaddAllon aList/Set/Queuereceiver that exists to spread into. No action required; any// ignore:markers added to work around this false positive can be removed.avoid_large_list_copyno 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 typedList, or bounded bytake(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_usageno 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-stateunit tests into the test build (they had drifted out and stopped compiling) and refreshed their fixtures; added coverage for the sharedisUpdatablepredicate and suppressed-package exclusion. - Added a
filesExpectedfield toviolations.json(the project-file denominator) so the extension can detect a partial analysis. - Documented the empty
cross_file_fixture/test/*_test.dartfiles 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
q0qhad 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_synchronouslyno longer flagssetStateafter anif (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 positivemountedguards. 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.gitignorerule 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 viagit rm --cached. - Consolidated the build backlog and planning index into
ROADMAP.mdand 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.yamland 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.jsonwithout 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/byImpactfields inviolations.jsonare 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-refreshstub 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 README —
extension/README.mdis gitignored and regenerated from the rootREADME.mdat publish time (same pattern already used forextension/CHANGELOG.md), so the package and extension descriptions can no longer drift apart.
13.9.1 #
Fixed #
avoid_positioned_outside_stackno longer fires whenPositionedis passed via aList<Widget>(orchild:) parameter to a custom widget that internally spreads it into aStack— e.g.FocusCard(backgroundLayers: [Positioned(...)])whereFocusCardis a user-defined card that hosts a Stack inside its ownbuild(). The ancestor walk now treats the direct custom-widget parent as indeterminate (its internal layout is invisible to static analysis); Flutter framework widgets likeColumn/Roware still walked past, so the real bugColumn(children: [Positioned(...)])continues to lint. No action required — remove any// ignore: avoid_positioned_outside_stackyou 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/+docsrows 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.audioplayersrendering 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 towidget.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,codeSizeJSON export field, "Code size" column tooltip copy). - Added
comparison-ranker.test.tsto the testtsconfiginclude 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 shipexample/,test/,tool/, ordoc/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 inexample/) 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-widthcustom 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 viasetProperty()at init from a duplicatedata-bar-widthattribute, 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.pyrunsdart pub getin the project root and in everypackages/*/with apubspec.yaml, ahead of the audit, format, analyze, and test gates. Previously a stale or missing.dart_tool/package_config.jsoninpackages/saropa_lints_api/surfaced as thousands of phantompackage:test/test.darterrors during the audit's analyze step, which forced a manual abort + two-directorydart pub get+ restart of the whole pipeline. - Translation pipeline (
extension/scripts/i18n/) modernized.generate_translations.pynow 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 writesextension/reports/i18n_translation_audit.mdwith 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 indictionaries.pyare 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.includeOtherAnalyzerFindingsInDashboardandsaropaLints.includeAnalyzerTodosInDashboard(default off); commandsSaropa Lints: Toggle Show Other Analyzer Findings on Dashboard,... Toggle Show Analyzer TODOs on Dashboard, and... Toggle TODO/HACK Workspace Scannerinvokable from the command palette. No action required.
13.7.2 #
Added (Extension) #
- Auto-analyze on dependency changes — the extension now watches
pubspec.lockand automatically re-runsdart analyzewhen dependencies change (afterpub get/pub upgrade), with a 10-second cancel-restart debounce to coalesce rapid lock-file rewrites. Controlled by the newsaropaLints.runAnalysisAfterDependencyChangesetting (default: on); toggle from the sidebar Settings row or command palette. No action required.
Maintenance
- Tracked
reports/organize_reports.pyin git by switching.gitignorefrom directory-level to content-level ignore with a negation rule; also addedexample*/reports/to.gitignoreso generated report output under example directories stays untracked.
13.7.1 #
Fixed #
avoid_string_substringno longer fires on indexOf-guarded, loop-bounded, or early-exit-guarded substring calls — the rule now recognizeswhile/forloop conditions, precedingif (idx == -1) returnguards, and if-conditions that reference substring arguments as evidence of bounds safety. No action required.- Analyzer v9
useDeclaringConstructorsAstcrashes resolved — all.namePart.typeNameaccesses (132 sites) and.namePart.typeParametersaccesses (4 sites) now use safenameToken/nameTypeParametersextensions that fall back to the pre-gate.name/.typeParametersAPI; additionally,_wrapCallbacknow catchesUnsupportedErrorglobally 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 getinpackages/saropa_lints_api/to resolve missingtestdependency; 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
useDeclaringConstructorsAstcrash — all 335+.body.memberscall sites now use safebodyMembers/bodyConstantsextensions that fall back to the pre-declaring-constructors API when the gate throws; projects onanalysis_server_plugin ^0.3.4withanalyzer 9.xcan rundart analyzewithout the plugin crashing. No action required. require_ios_face_id_usage_descriptionfalse 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 everyLocalAuthenticationcall site even whenNSFaceIDUsageDescriptionwas already configured; URI handling and Windows path normalization inInfoPlistCheckerare 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-visibilityso 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 guard —
npm run verify-nls-keys(underextension/) asserts every%…%placeholder fromextension/package.jsonresolves inextension/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 filled —
package.nls.*andsrc/i18n/locales/*now include machine-translated copy forbn,fa,fil,he,id,pl,sw,th,tr,uk, andvi(plus corrections forde,nl, andrudependency-count wording); regenerate withSAROPA_I18N_MACHINE_TRANSLATE=1if you fork strings. Hebrew uses Google target codeiwin 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 defaults —
dart run saropa_lints:cross_file …merges optionalsaropa_lints_cross_filesettings fromanalysis_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 fixes —
avoid_redundant_semantics,require_baseline_text_baseline, andavoid_unconstrained_dialog_columnnow offer IDE fixes (remove redundantSemanticsaround anImagethat already hassemanticLabel, inserttextBaselinewhen using baseline cross-axis alignment, and addmainAxisSize: MainAxisSize.minforColumninside dialogs); apply only where the suggestion matches intent; no config change.
Maintenance
- Contributor planning docs — assorted
plans/*.mdchecklists (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.uiLanguageto 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 catalogs —
uiLanguage.pick.*strings (quick pick title, placeholder, and Auto row) are translated in every shippedextension/src/i18n/locales/<locale>.json, with matching phrase keys inextension/scripts/i18n/dictionaries.pysogenerate_locales.pykeeps them after regeneration. No action required. - UI language scope (docs) —
extension/scripts/i18n/README.mdnow 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 workflow —
scripts/publish.py/ extension packaging: US English spelling check skipsextension/scripts/i18n/(translation data, not US-maintained prose); optional regeneration ofpackage.nls.<locale>.jsonand 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.jsonnow resolve throughpackage.nls.json, with generatedpackage.nls.<locale>.jsonfiles for additional languages. Shared webview strings live underextension/src/i18n/with a runtime locale picked from VS Code's display language orsaropaLints.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.lockchanges by 30s (was 5s) so a session of back-to-backpub upgradecalls 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 Vibrancy —
runScannow 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, andforceRefresh: trueis 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.lockagainst the persisted last-scan fingerprint and skips when bytes are unchanged.pub getagainst 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
latestResultsand 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}.yamlno longer pin the embedded plugin to the old^5.0.0-beta.8constraint 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 runspub upgradeagainst it — with the stale pin, anyone whose project also depends on a package requiring a newer analyzer (e.g.riverpod_lint ^3.1.3requiringanalyzer ^9.0.0) haddart analyzeabort 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 withpubspec.yamlon every major bump so they can't drift again. Reported as #216. No action required after upgrading; consumers usinginclude: package:saropa_lints/tiers/<tier>.yamlwill start resolving cleanly on the nextpub get.
Maintenance
- New module
scripts/modules/_tier_yaml_version.pyrewrites the saropa_lintsversion:line in eachlib/tiers/*.yamlat publish time, anchored to the current major frompubspec.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 unrelatedversion: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 undertool/— repo-local CLI utilities run viadart runand never execute on a Flutter UI isolate, so sync I/O is legitimate there. Mirrors the existing skip forbin/. No action required.
Maintenance
tool/rule_pack_audit.dartandtool/generate_rule_pack_registry.dart—applyCompositeRulePacksnow returns a new map instead of mutating its argument, clearing theavoid_parameter_mutationlint. Both call sites updated to consume the returned map. No change to extracted pack contents or generator output.- Plugin self-source
analysis_options.yamlexcludestool/**belt-and-braces, matching the existingbin/**exclusion. The cached plugin snapshot lags local edits toSaropaContext.isCliToolScript, so the host-level exclude preventsdart analyzenoise 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, andprefer_expression_body_getters.
Fixed (Extension) #
- Upgrade-check throttle now lets a newly-published
saropa_lintsversion 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") andsaropaLints.overview.copyAsJson("Copy Overview as JSON") — that were declared inpackage.jsonand 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 withcommand not found; the entries are now gone. - Version 13.4.2 was bumped in
pubspec.yamlbut 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.pynow refuses to publish when any## [X.Y.Z]section inCHANGELOG.mdhas an empty body — that was the exact shape that caused the rename-collision recovery inapply_version_and_rename_unreleasedto 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.pynow finds nested rule tests undertest/rules/{group}/, fixing the gap report that falsely listedwidget_patterns_avoid_prefer,structure,async,bloc, andperformanceas missing. The previous flattest/*_test.dartglob saw zero rule-category tests; coverage is now reported correctly (116/116 categories tested, 1095 test calls).scripts/modules/_extract_rule_messages.pynow extracts all 2165 rules instead of producing an empty JSON dump. Two bugs landed when the script was moved intoscripts/modules/: the flatglob("*_rules.dart")only matched the barrel export (zero LintCodes), and theparent.parentwalk pointed atscripts/lib/src/rules/— a non-existent path — so even fixing the glob alone would have returned zero. The CLI body is now guarded byif __name__ == "__main__":so importing the module no longer mkdirsreports/or writes a JSON file as a side effect.- Moved release notes for
12.5.2through12.6.1fromCHANGELOG.mdtoCHANGELOG_ARCHIVE.mdso the active changelog stays focused on the current13.xseries. 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, andextension/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 eachit()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.jsonwas written by an older saropa_lints plugin (any version <13.4.x with the legacycritical/high/medium/low/opinionatedimpact vocabulary). The reader now normalizes those values to the currenterror/warning/infobuckets 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_lintsdev-dependency, missinganalysis_options.yaml, malformed YAML inanalysis_options.yaml, or a bare top-levelsaropa_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 validinclude: package:saropa_lints/tiers/recommended.yamlline 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.