saropa_lints 12.3.3
saropa_lints: ^12.3.3 copied to clipboard
2134 custom lint rules with 254 quick fixes for Flutter and Dart. Static analysis for security, accessibility, and performance.
Changelog #
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
12.3.3 #
avoid_path_traversal and require_file_path_sanitization no longer flag private helpers whose tainted parameter only ever receives compile-time string literals, or whose caller resolves the path through a trusted Dart-SDK API (Isolate.resolvePackageUri, Directory.systemTemp, Platform.resolvedExecutable, Platform.script, Directory.current, File.fromUri / Directory.fromUri / Link.fromUri). avoid_null_assertion no longer flags the common RegExpMatch.group(N)! idiom — iterating allMatches / firstMatch over a literal regex and force-unwrapping a group is now recognized as a safe pattern. prefer_debug_print no longer pesters pure Dart packages to call a Flutter-only API.
Fixed #
avoid_path_traversal+require_file_path_sanitizationfalse positive on internal-resolver parameters (shared helperisFromPlatformPathApi). Both rules use a purely syntactic "parameter name appears in path source" heuristic whose one escape hatch — isFromPlatformPathApi — previously recognized only eight Flutterpath_providernames. That missed two large classes of safe code: (1) private helpers whose every call site passes aStringLiteral(e.g._sendWebAsset('assets/web/style.css')), and (2) parameters resolved from trusted Dart-SDK APIs that cannot carry HTTP-origin input without developer-written bridging. Two coordinated fixes in platform_path_utils.dart: (a)platformPathApisgainsresolvePackageUri,resolvedExecutable,systemTemp,Platform.script,Directory.current,File.fromUri,Directory.fromUrialongside the existingpath_providerallowlist — covers the resolver-parameter case; (b) new helperisParamPassedOnlyLiteralsAtCallSites(node, paramName)traces every same-scope call site of the enclosing private method and accepts the helper as safe only when every observed call site passesSimpleStringLiteral/AdjacentStrings/StringInterpolation-without-expressions at the tainted parameter's position (handles both positional and named params) — covers the literal-only call-site case. Conservatively returns false when zero call sites are observed (cannot prove all callers pass literals). Both AvoidPathTraversalRule and RequireFilePathSanitizationRule consume the new helper immediately after the existingisFromPlatformPathApicheck. HTTP-origin taint paths (public functions with user-input parameters, private helpers reachable from non-literal call sites) still lint — regression guards added to both fixtures. Bug reports: bugs/avoid_path_traversal_false_positive_internal_resolver_parameter.md, bugs/require_file_path_sanitization_false_positive_internal_resolver_parameter.md. Fixtures: avoid_path_traversal_fixture.dart, require_file_path_sanitization_fixture.dart.prefer_debug_printfalse positive on pure Dart packages (rule version v1 → v2). The rule told authors to replaceprint()withdebugPrint(), butdebugPrintlives inpackage:flutter/foundation.dart— so on a package that does not depend on Flutter (e.g.saropa_drift_advisor3.3.3, which has zero runtime dependencies by design), the recommendation was categorically unactionable: "fix it" meant "add Flutter as a dependency just to silence this lint," which is the wrong direction. Fix: PreferDebugPrintRule.runWithReporter now returns early whenProjectContext.getProjectInfo(...).isFlutterProjectisfalse, matching the existing gate in the sibling AvoidPrintInReleaseRule in the same file. Flutter projects see unchanged behavior. Five false-positive sites reported insaropa_drift_advisor(lib/src/error_logger.dart,lib/src/drift_debug_server_io.dart) stop firing. Rule version bumped{v1}→{v2}andUpdated:→v12.3.3. Fixture prefer_debug_print_fixture.dart gains a pure-Dart regression guard (_pureDartNoLint) and the existing BAD/GOOD pair now documents that the example package itself is pure-Dart, so the recommendation shape is retained without assertingexpect_lint. Bug report: bugs/prefer_debug_print_false_positive_pure_dart_package.md. (debug_rules.dart)avoid_null_assertionfalse positive onRegExpMatch.group(N)!(rule version v7 → v8). The rule previously fired on everymatch.group(1)!/match.group(2)!insidefor (final match in regex.allMatches(s))andfinal match = regex.firstMatch(s); if (match != null) match.group(1)!— pushing authors to add dead?? ''fallbacks. Triggered in real code insaropa_drift_advisor's_parseCallerFramewhere the regexr'#\d+\s+\S+\s+\((.+?):(\d+):\d+\)'has two explicit, non-optional capture groups sogroup(1)/group(2)are statically non-null on a successful match. Fix:AvoidNullAssertionRulegains a fifth safe-pattern predicate_isSafeRegExpMatchGroup(node)that recognizes<receiver>.group(<nonNegativeIntLiteral>)!when the receiver's static type resolves todart:coreRegExpMatchorMatch. Intentional narrow heuristic (Hypothesis B in the bug report) — the rule does not inspect the regex pattern string to count non-optional groups, so a user-written regex with an optional capture group like(a)?bfollowed bymatch.group(1)!is an accepted miss. Rationale:avoid_null_assertionis INFO severity, the optional-group case is rare, and pattern-string inspection is expensive and brittle (alternations, nested optionals, named groups). Rule version bumped{v7}→{v8}andUpdated:→v12.3.3. Fixture avoid_null_assertion_fixture.dart gains three new cases: theallMatchesloop, thefirstMatchguard, and the accepted-miss optional-group case (documented as a known limit). Bug report: bugs/avoid_null_assertion_false_positive_regex_match_group.md. (type_rules.dart)
12.3.2 #
Fixed #
- Plugin-internal code cleanup:
dart analyze --fatal-infospasses on the package's own source. The pre-publish audit was blocked by 4,106 issues (468 warnings + 3,638 infos) produced when the package's own rules ran against the plugin's own implementation. Many were categorical false positives — the rules target consumer Flutter / Dart application code patterns (SSRF guards, stack-trace-to-UI leaks, UTC-for-storage, iOS deployment-target drift, cache-TTL requirements) which do not apply to a Dart-VM analyzer plugin, its CLI tooling in bin/, and rule-definition source whose string literals match rule-name patterns. Changes:- Real code fixes in lib/src/, bin/, test/ and tool/: safe substring helpers via new string_slice_utils.dart (
prefix/suffix/slice/afterIndex/afterPrefix),.toUtc()at everytoIso8601String()storage-adjacent call site (scan timestamps, plugin log entries, LRU cache_lastRelieve, incremental-prioritysavedAt, batch JSONu), null-coalescing defaults at nullable interpolations,on Object catchreplacing barecatch,developer.log(..., stackTrace: st)replacing swallowed exceptions in baseline / rule-pack / cache-stats / violation-export paths, refactoredCompilationUnitDerivedDataanalysis to return-not-mutate,_LruNode.linkAsHead(...)method so linked-list updates happen as instance state rather than parameter mutation, boundedLazyPatternCachewith 2048-entry ceiling and full-clear eviction, explicitAnalysisReporter.dispose()for the debounce [Timer],lastOrNull+ null guard in tool/rule_pack_audit.dart, andhasLength(...)test matchers replacingexpect(list.length, N).string_slice_utils.dartis wired as an import in every file that used.substring(...)— the extension methods clamp indices so previously-throwing range calls now return an empty / clamped slice. - Dogfood-only rule disables in analysis_options.yaml (scope: this package's own
dart analyzerun — consumers and the shippedplugins: saropa_lints:manifest are untouched):avoid_platform_specific_imports,avoid_global_state,require_ios_deployment_target_consistency,avoid_stack_trace_in_production,require_catch_logging,require_cache_expiration,avoid_path_traversal,require_file_path_sanitization,require_url_validation,avoid_money_arithmetic_on_double,avoid_unbounded_cache_growth, plus the stylistic / opt-in info rules (prefer_blank_line_*,prefer_capitalized_comment_start,prefer_sentence_case_comments,prefer_period_after_doc,prefer_readable_line_length,prefer_no_commented_out_code,prefer_debug_print,prefer_final_locals,prefer_static_method,move_variable_closer_to_its_usage,require_test_description_convention,avoid_misused_set_literals,avoid_null_assertion,avoid_ignoring_return_values,require_file_exists_check,avoid_large_list_copy,avoid_nested_assignments,avoid_variable_shadowing,avoid_very_long_length_files,prefer_setup_teardown,prefer_boolean_prefixes,prefer_doc_comments_over_regular,prefer_null_aware_method_calls,prefer_where_or_null,prefer_const_declarations,prefer_inlined_adds,prefer_json_serializable,avoid_redundant_async,avoid_duplicate_test_assertions). Each disable carries an inline justification comment explaining the specific plugin-infrastructure pattern that makes the rule categorically a false positive on this source (CLIprint(), analyzer!on framework-guaranteed non-null fields, plugin session caches,{}inside already-typedMap<K, Map<…>>, etc.).
- Real code fixes in lib/src/, bin/, test/ and tool/: safe substring helpers via new string_slice_utils.dart (
Maintenance
- Publish script: New interactive mode
7) Publish existing .vsix (skip packaging; newest in extension/). Motivated by the 12.3.0 → 12.3.1 → 12.3.2 version-drift seen during the 12.3.1 hotfix:publish.pyauto-bumpspubspec.yamlandextension/package.jsonto the next version after a successful pub.dev publish, so re-running mode 6 (Extension only) to finish an interrupted extension publish would repackage the .vsix at the bumped (unpublished) version —saropa-lints-12.3.2.vsixwhile pub.dev was at12.3.1— and shipping that would mismatch the Dart plugin version on Marketplace / Open VSX. Mode 7 skips the repackage step entirely: it scansextension/*.vsixsorted by mtime (newest first), selects the newest, and hands it straight to the existing install/Marketplace/Open VSX prompts. If multiple .vsix files exist, all are listed with the selected one flagged. Hard-errors (no auto-fix) whenextension/is missing or contains zero.vsixfiles (user should run mode 6 first to package one). Reuses_prompt_extension_install_and_publish()andpublish_extension()so install + store publish behavior is identical to mode 6. (publish.py, _publish_workflow.py)
12.3.1 #
Hotfix on top of 12.3.0: the scan CLI (and any caller that invoked the plugin with a tier) crashed with Unsupported operation: Cannot change an unmodifiable set as soon as the second source file was visited, because tier rule-sets are const and the plugin's rule-pack reloader tried to mutate them in place. 12.3.0 was tagged but never actually published to pub.dev because this surfaced on the CI test step — republishing as 12.3.1 with the fix. — log
Fixed #
- Plugin crash on second analyzed file when enabled rules came from a tier (
essential,recommended, …).ScanRunner._resolveRuleNames()returns the exactconst Set<String>literal from tiers.dart (e.g.essentialRules) and assigns it directly to the staticSaropaLintRule.enabledRules. Later, on every analyzed file,ProgressTracker.recordFilecallsloadRulePacksConfigFromProjectRoot, which calls_reloadRulePacksFromRoot— that function readSaropaLintRule.enabledRulesback asenabledand then calledenabled.removeAll(_packContributedCodes!)(andenabled.add(code)viamergeRulePacksIntoEnabled). Both mutations throwUnsupported operation: Cannot change an unmodifiable setagainst aconstset. The crash was order-dependent: the first call through the function took the early-returncontent == nullbranch and only set_packContributedCodes = {}, so the failure only materialized on the second call into_reloadRulePacksFromRoot(when_packContributedCodeswas non-null andremoveAllactually got executed). That's why the bug slipped through local testing —dart testhappens to schedule thescan_runner_test.dartcases in an order that either avoids the second call or avoids a project root with a rule-packs block; CI's parallel test runner hit the failing ordering and blew up with##[error]8507 tests passed, 1 failed.ontest/scan_runner_test.dart: ScanRunner run with tier returns non-null list, which aborted the publish workflow before thepub publishstep ran. Fix:_reloadRulePacksFromRootnow takes a mutable copy (final enabled = <String>{...?SaropaLintRule.enabledRules};) at function entry, soremoveAll/addalways succeed regardless of whether the source set is aconsttier literal, aSet.union(...)composite, or a previously-assigned mutable set. The fix is defensive at the mutation site rather than the single known producer (ScanRunner), so any future caller that assigns an unmodifiable set toenabledRulesstays safe. (config_loader.dart)
12.3.0 #
Windows users get their vibrancy reports back (CLI spawn + transitive footprint fixes), the plugin now logs to reports/.saropa_lints/plugin.log so silent failures are visible, and the report toolbar gains Rescan, Open Project, and Copy All JSON buttons — plus a new prefer_listenable_builder rule. — log
Fixed #
- Extension: Vibrancy scan CLI commands (
dart pub outdated --json,dart pub deps --json) silently failed on Windows, which produced a cascade of silent downstream failures: the Transitives column was hidden for every project, the Footprint toggle (Own / +Unique / +All) showed identical numbers, and upgrade-blocker analysis was skipped. Root cause: Flutter installs shipdart.bat(notdart.exe) on Windows, and Node'sexecFile('dart', ...)does not consultPATHEXTthe way a shell does — so even whendart pub deps --jsonran fine from a terminal, the extension's child process failed withENOENT. The failure surfaced as a single log lineBlocker analysis skipped — CLI commands failedwith no underlying reason. Fix: (a) bothrunDartCommandandrunFlutterCommandin flutter-cli.ts now passshell: trueonwin32so cmd.exe resolves.bat/.cmdextensions. Args are all hardcoded (['pub', 'deps', '--json']) andcwdis a workspace folder path, so there's no shell-injection surface. (b)CommandResultgained anerrorMessagefield;DepGraphResultandPubOutdatedResultpropagate it; blocker-enricher.ts now logs concrete stderr (pub outdated: ... | pub deps: ...) atERRORlevel so the log self-diagnoses future regressions. (flutter-cli.ts, dep-graph.ts, pub-outdated.ts, blocker-enricher.ts) - Extension: Vibrancy report Footprint toggle (Own / + Unique / + All) silently showed identical sizes across all three modes even when the Transitives column was populated. Root cause:
enrichTransitiveInfocomputesuniqueTransitiveSizeBytesandsharedTransitiveSizeBytesby looking each transitive up in asizeLookupmap — but the map was built only from direct-dep results (since only direct deps get full vibrancy analysis). Transitives likeimage(pulled in bycrop_your_image) were absent from the map, sosawAnySizestayed false and both size fields ended upnull. All three computed labels (own,own + unique,own + unique + shared) collapsed toown. Fix: extension-activation.ts now collects the union of every transitive package name across all direct deps (excluding those already insizeLookup), fetches their archive sizes in parallel via the existing cachedfetchArchiveSize, and merges them into the lookup before callingenrichTransitiveInfo. First scan in a project pays N parallel HEAD requests against pub.dev; subsequent scans are zero-cost becausefetchArchiveSizecaches underpub.archiveSize.<name>. Progress notification now reports "Fetching N transitive sizes...". (extension-activation.ts) - FATAL plugin silence affecting all consumers — saropa_lints emitted zero diagnostics when the analyzer was launched with a cwd different from the consumer's project root (e.g. every VS Code user who opened their workspace via the file picker, every Flutter project where the analyzer inherited a parent cwd). Three interlocking bugs produced a silent, hook-less failure mode where the plugin successfully handled
analysis.setContextRootsandanalysis.updateContentrequests but never emitted a single diagnostic. Root cause chain: (1)analysis_server_plugin'sPlugin.registeris called synchronously in thePluginServerconstructor — beforestart(), before the channel, before any context-root info — so the plugin cannot know the consumer project root at registration time. (2)SaropaLintsPlugin.start()calledloadNativePluginConfig()with no project-root argument, which readanalysis_options.yamlrelative toDirectory.current.path— which for analyzer-launched plugins is the analysis-server process's cwd, not the consumer project. The read returned null and_loadDiagnosticsConfigsilently early-returned, leavingSaropaLintRule.enabledRules = null. (3)registerSaropaLintRuleshad a kill-switch:if (enabled == null || enabled.isEmpty) return;— so zero rules were ever registered with thePluginRegistrywhen config read failed.register()runs once, so there was no recovery path. Fix: (a)registerSaropaLintRulesnow registers every rule unconditionally at plugin-init (all ~2100), honoringdisabledRules/configAliasesas before. Per-rule enablement is then filtered by the analyzer's ownRegistry.ruleRegistry.enabled(diagnosticConfigs)— the analyzer parsesplugins > saropa_lints > diagnostics:natively and only installs AST visitors for rules markedtrue, so the 2100-rule registration has zero hot-path cost: disabled rules never reachregisterNodeProcessorsand never dispatch a single visitor callback. Register-time cost is ~2100 lightweight rule-instance allocations (~1 MB, permanent). (b) NewloadNativePluginConfigFromProjectRoot(projectRoot)in config_loader.dart reloads all config (severities, diagnostics, rule packs, baseline, banned-usage, output) from a known project root. (c)SaropaContext._wrapCallbacklazily invokes this loader on the first visitor call that has a usable file path — the real project root is derived viaProjectContext.findProjectRoot(filePath)(walks up to nearestpubspec.yaml). This piggyback is essential for severity-override, rule-pack, and baseline features to work in the IDE-launched path. (d)_loadDiagnosticsConfignow logs viadeveloper.logwhenanalysis_options.yamlis missing or lacks aplugins > saropa_lints > diagnostics:block, so the "plugin loaded but silent" failure mode is observable. New test suitetest/native/config_loader_project_root_test.dartexercises the project-root-aware loader; the existingsaropa_plugin_registration_test.dartwas updated to match the new "register all, let analyzer filter" semantics. (saropa_lints.dart, config_loader.dart, saropa_context.dart)
Added #
-
User-visible plugin log at
reports/.saropa_lints/plugin.logso consumers can see what the analyzer plugin is doing without spelunking%LOCALAPPDATA%\.dartServer\logs\(Windows) or~/.dartServer/logs/(macOS/Linux). The new PluginLogger buffers log events emitted before the project root is known (duringPlugin.start()— see the FATAL fix above for why the root isn't known at that point), then flushes them to disk as soon asSaropaContext._wrapCallbackresolves the real project root on the first analyzed file. Every significant plugin event now writes a line the user can tail:Plugin.start() — loading initial config,Plugin.register() — registering rules with analyzer,registerSaropaLintRules: registered N rules (M candidates, K disabled),Config loaded from <path> — enabledRules: N, and every silent-failure path in config_loader.dart (missinganalysis_options.yaml, missingplugins > saropa_lints > diagnostics:block, I/O errors). Logs are best-effort — write failures are swallowed so a read-only filesystem or permission issue cannot kill the analysis isolate.developer.logmirroring is preserved for CI log harvesting. New unit tests in plugin_logger_test.dart cover: buffering before root, flush on root, post-root direct-to-disk, idempotent setProjectRoot (first root wins), empty-root no-op, error + stack trace formatting. (plugin_logger.dart, config_loader.dart, saropa_context.dart, main.dart, saropa_lints.dart) -
Rule
prefer_listenable_builder(Recommended tier, INFO): FlagsAnimatedBuilder(animation: <plainListenable>, ...)and recommendsListenableBuilderinstead. The rule only fires when the analyzer can resolve theanimation:argument's static type and that type implementsListenablebut is not a subtype ofAnimation— soAnimationController,CurvedAnimation,Tween.animate(...)results, and customAnimationsubclasses are correctly left alone.ValueNotifier,ChangeNotifier, and customListenableimplementations trigger the migration hint. Ships a one-token quick fix that renames the constructor (the two widgets share the sameanimation:/builder:/child:parameter surface). Runtime-gated byProjectContext.flutterSdkAtLeast(3, 13, 0)so projects pinned below Flutter 3.13 (whenListenableBuilderwas added) are silently skipped. Promoted from plan/054. (animation_rules.dart, prefer_listenable_builder_fix.dart) -
ProjectContext.flutterSdkAtLeast(filePath, major, minor, patch)— new helper that parsesenvironment.flutterfrompubspec.yamland returns whether the project's declared minimum Flutter SDK is ≥ the requested version. Handles exact ("3.13.0"), caret ("^3.13.0"), range (">=3.13.0 <4.0.0"), and pre-release ("3.13.0-0.0.pre") constraint forms; returnstrue(assume modern) for missing /any/ unparseable constraints so rules still fire by default on unusual pubspec formats. Reusable gate for future SDK-migration lints. (project_context_project_file.dart) -
Extension: Vibrancy report toolbar now has a Copy All JSON button that copies every row's full record — including all expander content (health factors with letter grade, full vulnerability list, every file reference, the complete transitive dependency list with shared markers, and pub.dev/repo links) — as a JSON array to the clipboard. The per-row JSON was also enriched with
health.grade,transitives.deps, andtransitives.sharedDepsso the single-row copy button produces the same shape. (report-html.ts, report-script.ts) -
Extension: Vibrancy report toolbar now has a Rescan button that runs
Package Vibrancy: Scanwithout leaving the report. Previously, refreshing required opening the Command Palette; the button posts arescanmessage to the host, which awaits the scan and then re-invokesshowReportso the open panel picks up the fresh results in place. The button is disabled while a scan is in flight and reverts automatically when the webview rebuilds (with a 60s fallback for canceled scans). (report-html.ts, report-script.ts, report-webview.ts) -
Extension: New command
Open Another Project for Vibrancy Scan...(saropaLints.packageVibrancy.openOtherProject) and matching Open Project… toolbar button on the Vibrancy report. Pops a file picker scoped topubspec.yaml, then opens the selected file's parent folder in a new VS Code window viavscode.openFolderwithforceNewWindow: true. Lets you diagnose multiple Flutter/Dart projects (e.g. compare transitive resolution across repos) without closing the current workspace — each window gets its own extension host and runs the scan against its localpubspec.lock,.dart_tool, and source tree. (extension-activation.ts, package.json, report-html.ts, report-script.ts, report-webview.ts)
Changed #
- Extension: Vibrancy Command Palette entries are now consistently grouped under a single
Saropa:category and renamed for brevity. Previously, typing "vibrancy" returned a grab-bag — one entry had aSaropa Lints:prefix (the auto-generated view-focus command), the rest had no prefix, and titles drifted between "Package Vibrancy", "Vibrancy Report", and "Vibrancy Scan". Now all palette-visible vibrancy commands share the"category": "Saropa"field and use short, consistent titles:Scan Packages,Show Report,Export Report,Scan Another Project...,Clear Cache,Browse Known Issues,Export SBOM,Toggle Badges. The redundantShow Vibrancy BadgesandHide Vibrancy Badgespalette entries (which were already state-gated to show one at a time) are now hidden from the palette entirely —Toggle Badgesis the sole entry for that function. No command IDs changed, so keybindings and external references continue to work. (package.json) - Extension: Vibrancy report Stars column was replaced with Likes (pub.dev like count) and a new Downloads column (pub.dev
downloadCount30Days). GitHub stars are a repository-level signal, so every package published out of a monorepo (e.g.firebase/flutterfire,bloclibrary/bloc,flutter/packages) reported an identical star count — which was misleading when comparing packages in the same project. Pub.dev likes and 30-day downloads are per-package, so the two new columns give a true package-specific trust signal. Both cells link to the package's pub.dev/scoretab (e.g.https://pub.dev/packages/crop_your_image/score). Numbers use a compact format (8.3M,1.2k) with the full count in the cell tooltip. The per-row JSON (Copy Row / Copy All JSON) still surfaces the GitHub star count — now as a structuredstars: { count, repoUrl, monorepoSiblings }block, wheremonorepoSiblingscounts how many other packages in the same project share the repo URL (0 = dedicated repo, N > 0 = monorepo context so the raw count should be discounted). Row JSON also gained top-levellikes,downloadCount30Days, and alinks.scorepub.dev score URL. (report-html.ts, pub-dev-api.ts, types.ts) - Extension: Vibrancy report Update column is now right-aligned, matching the other numeric/compact columns (Likes, Downloads, Issues, PRs, Size, References, Transitives, Deps). Both the update-arrow cells (e.g.
\u2192 2.0.0) and the dimmed en-dash placeholder now align on the right edge. (report-html.ts)
Fixed #
- Extension: Vibrancy report References column no longer double-counts a source file when it both imports and re-exports the same package. Example:
lib/utils/system/share_utils.dartcontains bothimport 'package:share_plus/share_plus.dart';(internal use) andexport 'package:share_plus/share_plus.dart' show XFile, ShareResult, ShareResultStatus;(public API re-export). The import scanner previously emitted onePackageUsageper directive, so the cell rendered2even though only one physical file references the package. The scanner now merges same-file usages into a single entry keyed by(filePath, isCommented), with the directive locations split into newimportLineandexportLinefields onPackageUsage. The References cell tooltip, detail section, package detail panel, and both JSON exports (Copy All JSONon the toolbar and the markdown/JSONreport-exporter) now show both line numbers — e.g. JSONfiles: [{ "path": "lib/utils/system/share_utils.dart", "import": 7, "export": 1 }]— while the file-level count stays accurate. (import-scanner.ts, report-html.ts, package-detail-html.ts, report-exporter.ts) - Extension: Vibrancy report and Known Issues search boxes now trim leading/trailing whitespace from the query, and each gained an inline clear (×) button that appears as soon as there is text in the field. Clicking the × empties the input, re-runs the filters, and returns focus to the search box. Pasting a package name from a terminal, pubspec excerpt, or wrapped text list previously swallowed all matches because the row data values have no surrounding whitespace, while the search compared the raw input — copy-paste now works regardless of surrounding spaces, tabs, or newlines. (report-html.ts, report-script.ts, report-styles.ts, known-issues-html.ts, known-issues-script.ts)
- Extension: Vibrancy report header gauge now actually fills to its target arc length. The static CSS rule
.gauge-fill { stroke-dasharray: 0 999 }was overriding the inline SVGstroke-dasharrayattribute (CSS rules trump SVG presentation attributes), so the gauge always rendered empty even when the project grade was B/C/D/E. The inline--gauge-target/--gauge-arcCSS variables set on the<circle>are now consumed by the rule, and the load-time fill animation moved to a@keyframesso the resting state actually paints. (report-styles.ts) - Extension: Violations view file rows now open the file on click. Previously each
FileItemonly setresourceUri, so clicking the filename merely toggled the row's expand/collapse state; avscode.opencommand is now attached when the file exists on disk, so the row opens the file directly while the expand triangle still shows the per-line violations. Moved/deleted files remain non-clickable (the existing "(file moved or deleted)" tooltip already explains the stale state, and attempting to open a missing file would surface a confusing error). (issuesTree.ts)
Maintenance
- Plan housekeeping (
plan/deferred/): Reviewed all 66 auto-generated SDK release-note plans (005-*.mdthrough134-*.md). Only one — #054 (prefer_listenable_builder_over_animated_builder) — had a defensible detection path; promoted it toplan/054-prefer_listenable_builder_over_animated_builder.mdwith full detection strategy, quick-fix plan, SDK-gate note (Flutter 3.13+), and false-positive guards (must not fire onAnimationsubtypes). The other 64 files — Flutter engine/tooling internals, CI/build infra, docs-only notes, Dart-Code VS Code extension features, and deprecations already covered bydeprecated_member_use— were archived verbatim (viagit mv) intoplan/deferred/_archive/. Nothing was deleted; original PR descriptions and labels are preserved. - Plan housekeeping (
plan/deferred/): Consolidated 66 individual plan files into a single landing doc atplan/deferred/sdk_release_notes_review.mdwith a verdict table grouped by rejection category and per-plan one-liner. Removed the redundantplan/deferred/README.md— its category-file index (compiler_diagnostics,cross_file_analysis,external_dependencies,framework_limitations,unreliable_detection,not_viable,plan_additional_rules_41_through_50) and the "before adding a new entry" checklist were folded into the review doc so there is one landing page instead of two. - Publish script: Pre-publish audit no longer auto-aborts when British English spellings are found. The spelling check now prints the report and prompts [R]etry (re-scan after fixing) or [I]gnore (continue the publish with the hits in place). Default on empty input is Retry, the safer option; Ctrl+C still aborts. Previously the only recourse was "fix every hit then re-run the entire 30-second audit" — minor user-facing copy could block a release even when the spelling was intentional (e.g. quoting a third-party API, product-name casing). When the user chooses Ignore, the consolidated audit reports the check as a ⚠ warning (not ✗ fail) so the decision is visible in the run log; when Retry is chosen, the script rescans in-place without re-running any other audit step. (_publish_steps.py)
scripts/publish.py— display the Saropa logo before prompting for the publish mode. The interactive menu (Full publish,Audit only, etc.) was printed beforeshow_saropa_logo(), violating the "logo always comes first" rule for Saropa scripts.main()now acceptsmode=Noneand prompts interactively after setup + logo;__main__just callsmain().- Tests: Harden the teardown of the
use_existing_variableintegration test (test/code_quality_rules_test.dart) against a Windows-only flake. The test creates a temp package underbuild\test_tmp\, spawnsdart pub get+dart analyzesubprocesses, then deletes the temp directory inaddTearDown. On Windows, directory handles from the exited subprocesses can linger briefly afterProcess.run()returns, soDirectory.delete(recursive: true)would fail withPathAccessException: ... errno = 32(EBUSY) and error the test even though all assertions passed. The teardown now retries up to 5 times with exponential backoff (100 ms → 1600 ms) and swallows the final failure — leaving a temp dir is harmless because the OS cleans%TEMP%andbuild/test_tmp/eventually, and a cleanup race should never fail an otherwise-passing test.
12.2.1 #
Publish script now verifies Marketplace and Open VSX separately, so an expired Marketplace token surfaces a concrete ACTION REQUIRED warning and auto-opens the manage page instead of a silent 0-exit. — log
Maintenance
- Publish script (
scripts/publish.py): Store-publication verification now reports Marketplace and Open VSX results separately. When the Marketplace times out on the expected version, the script prints anACTION REQUIREDwarning with the manage URL (https://marketplace.visualstudio.com/manage/publishers/Saropa) and the exact.vsixfilename to upload, then auto-opens the manage page in the default browser. Open VSX failures surface their own warning. Motivating case:vsce publishexiting 0 while the Marketplace never actually serves the new version (expired PAT / missing scope).
12.2.0 #
Letter-only grading for package vibrancy across the report and detail views, plus a new "true footprint" view that links shared dependency size into per-package cost. — log
Added #
- Quick fixes (Batch 12): Added 10 new quick fixes (9 new producers, 1 reuse) for previously fix-less rules:
avoid_redundant_positional_field_name(record_pattern) — deletes the redundant$Nname from a positional record field.prefer_wildcard_pattern(record_pattern) — replaces an unused pattern variable name (unused,ignore, …) with_.prefer_wildcard_for_unused_param(naming_style) — renames an unused positional parameter to the_wildcard.avoid_non_null_assertion(type_safety) — reuses the existingRemoveNullAssertionFixto strip the!operator.prefer_const_constructor_declarations(class_constructor) — insertsconstbefore a generative constructor.prefer_const_constructors_in_immutables(class_constructor) — insertsconston the first non-const generative constructor in an @immutable / Widget class.prefer_final_fieldsandprefer_final_fields_always(class_constructor) — addsfinalto a mutable instance field (replacing a leadingvarwhen present).avoid_double_and_int_checks(control_flow) — rewritesx is int || x is double/&&to the equivalentx is numcheck.deprecated_new_in_comment_reference(documentation) — strips the deprecatednewkeyword from[new Foo]doc references.
- Rule:
prefer_type_sync_over_is_link_sync(WARNING, Recommended tier) — flags staticFileSystemEntity.isLinkSync(path)calls, which returnfalseunconditionally on Windows per documenteddart:iobehavior and silently break cross-platform symbolic-link checks. Suggests the portable replacementFileSystemEntity.typeSync(path, followLinks: false) == FileSystemEntityType.link. Plan #079. - Rule:
avoid_removed_js_number_to_dart(WARNING, Recommended tier) — flags the removedJSNumber.toDartgetter fromdart:js_interop(Dart SDK 3.2). Surfaces a more actionable migration message than the analyzer default, directing developers to the type-explicittoDartDouble(floating-point) ortoDartInt(integer) getters. No auto-fix because the numeric target type is a semantic choice. Plan #090. - Extension: Footprint-mode toggle (Own / + Unique / + All) in the vibrancy report toolbar — switches what the Size column shows: the package's own archive (default), own + transitives used only by this dep (the size you'd save by removing it), or own + all transitives including ones shared with other direct deps. Sorting by Size respects the active mode.
- Extension: "True Footprint" row in the package detail panel — for any direct dep with transitives, surfaces the breakdown as
unique · +shared = totalwith a tooltip explaining how much disappears if you remove the dep vs. how much stays pulled in by other deps. Lets you spot cases likecrop_your_imagewhere the bulk of the size comes from a sharedimagetransitive. - Extension:
TransitiveInfo.uniqueTransitiveSizeBytesandsharedTransitiveSizeBytesfields, computed inenrichTransitiveInfofrom the per-package archive sizes already gathered during scan. - Extension: Re-export awareness throughout the vibrancy report. Each
PackageUsagenow carries anisExport: booleanflag. The Single-use summary card excludes packages whose only reference is aexport 'package:...'directive (they're public-API surface, not removable). The detail panel tags individual re-export lines with a "re-export" badge and a "public API surface" header note. The report row's References cell shows a "↪" badge after the count and prepends a warning to its tooltip when re-exported, and the muted single-use styling no longer applies to re-exports.hasActiveReExport()helper exposed alongsideactiveFileUsages(). - Extension: Startup-scan skip-gate. The package vibrancy scan that runs on every VS Code restart now persists a fingerprint of the last successful scan (sha256 of pubspec.lock + scoring weights/allowlist/repo-overrides/publisher-trust-bonus, plus the result snapshot). On the next startup, when the lock file and scan-config inputs are unchanged AND the prior scan finished within the configured skip window, results are silently rehydrated and the progress notification is suppressed. Falls back to a normal scan on any cache miss, schema mismatch, malformed blob, or clock skew.
- Extension: New setting
saropaLints.packageVibrancy.startupScanSkipTtlMinutes(default 60, min 0, max 10080 = one week). Skip-window for the startup scan in minutes. Set to0to always run the startup scan and disable skipping entirely. - Extension: New setting
saropaLints.packageVibrancy.showStartupScanSkipStatusBar(default false). When the startup scan is skipped, briefly show a status bar item (✓ Vibrancy: cached (Nm)) so users notice the skip; clicking it triggers a fresh scan. Off by default — silent rehydrate is the point. - Extension: Existing
saropaLints.packageVibrancy.cacheTtlHourssetting (declared but previously unused) is now wired toCacheServiceso the per-package pub.dev/GitHub response cache TTL honors the user's configured value (default 24 hours). - Extension: Clear Cache command (
saropaLints.packageVibrancy.clearCache) now also clears the persisted last-scan fingerprint so the next startup performs a fresh scan instead of silently rehydrating stale cached results.
Fixed #
- Extension: Vibrancy report column headers no longer wrap to two lines when many optional columns are visible. Headers, right-aligned numeric cells, and the footprint-mode toggle buttons now use
white-space: nowrapso each value stays on a single line. - Extension: Vibrancy report version-age suffix no longer shows
(new)for packages published within the last month. The label was misleading (a recently published version of a mature package isn't "new") and didn't carry useful information, so the suffix is now omitted entirely under one month.
Changed #
- Extension: Vibrancy report Category column now shows the letter grade badge only — the category label ("Vibrant", "Stable", etc.) and the dimmed
(n/10)suffix were removed. Full label and score breakdown remain available via the hover tooltip. - Extension: Report summary filter cards (Vibrant/Stable/Outdated/Abandoned/End-of-Life) now display the grade letter (A/B/C/E/F) as their label. The full category name moved to a
titletooltip on each card. - Extension: Report average-score summary card renamed to "Project Package Grade" and now shows a single letter derived from the average vibrancy score, replacing the old
n/10value. - Extension: Radial gauge in the report header now displays the project package grade letter instead of the
n//10stack. Tooltip label updated to "Project Package Grade". - Extension: Sidebar detail view header replaced the
n/10score pill plus standalone category-badge with a single letter pill. Category name is surfaced via the pill'stitletooltip. - Extension: Package detail panel header badge (top-right) now shows the letter grade only; the
n/10score and inline category label were dropped (label moved to the title tooltip). - Extension: Expanded row "Health Score" detail card dropped the redundant "Overall" numeric row — the aggregate is already shown as the letter badge in the card header; the factor rows (Resolution Velocity, Engagement Level, Popularity, Publisher Trust) remain.
- Extension: Health breakdown tooltip (shown on hover over a row's grade cell) now leads with "Grade: X" instead of "Vibrancy Score: n/10". Factor rows unchanged.
- Extension: CodeLens titles changed from "emoji n/10 Label" to "emoji X" (letter). The
indicatorStyle: textvariant now shows only the text indicator since a letter next to the text label was redundant;indicatorStyle: noneshows the letter alone. - Extension: pubspec hover tooltips show "X Category" (letter + label) in place of "n/10 Category". Alternatives list shows "(X)" per alt (letter derived from the alt's score via
scoreToGrade). - Extension: Diagnostic messages trail with "(X)" (grade letter) instead of "(n/10)". Applies to Review/Monitor/Deprecated verbs and to blocker annotations.
- Extension: Vibrancy tree view blocker row switched from "score (category)" to a single letter grade. Alternatives group shows "(X)" per suggestion.
- Extension: Package comparison view row renamed "Vibrancy Score" → "Vibrancy Grade"; cell displays the letter derived from the 0-100 score (ranking still uses the numeric score so ordering stays precise).
- Extension: Markdown report export renamed the "Score" column to "Grade" and displays the letter. The JSON sibling preserves the numeric
health.scorefield unchanged so downstream automation keeps working. - Extension: Budget-exceeded message for the
minAverageVibrancyrule now reads "Project Package Grade X is below minimum Y" instead of showingn/10actual vs limit. - Extension: DetailLogger output channel prints "name — X (Category)" and "Blocker grade: X" instead of
n/10forms. - Extension: New
scoreToGrade(score)helper incategory-dictionary.ts, re-exported fromstatus-classifier, providing a single source of truth for score-to-letter thresholds used by the gauge, summary card, alternatives, comparison view, and budget messages.
12.1.0 #
Vibrancy report gets a radial health gauge, A–F letter grade badges, expandable detail cards with score breakdowns, keyboard navigation, and a new Deps column highlighting shared transitives. — log
Added #
- Extension: Report renamed from "Package Vibrancy Report" to "Saropa Package Vibrancy" with the extension version shown as dimmed text next to the title.
- Extension: Animated radial gauge in the report header (floating top-right) showing the overall project health score on a color-coded 270-degree arc that fills on load.
- Extension: Letter grade badges (A through F) in the Category column, synced with the extension's category dictionary (A=Vibrant, B=Stable, C=Outdated, E=Abandoned, F=End-of-Life). Displayed as color-coded pill badges alongside the category label.
- Extension: Expandable detail cards — click any row (or press Enter with keyboard focus) to reveal an inline card with score breakdown, vulnerability list, file references, transitive dependency cloud, and external links. Collapse with a second click or Escape.
- Extension: Keyboard navigation in the report table — arrow keys (or j/k) move a visible focus highlight between rows, Enter/Space toggles expansion, Escape collapses all.
- Extension: New "Deps" column showing transitive dependency count per package with a tree icon. Shared dependencies are highlighted with a badge, and a tooltip lists all transitives with shared ones marked.
- Extension: Detail card dependency cloud highlights shared transitive deps in bold with a "shared" badge, so blast-radius of package removal is immediately visible.
Fixed #
- Extension: Radial gauge grade thresholds now match the category classifier boundaries (>=70 Vibrant/A, >=40 Stable/B, >=20 Outdated/C, <20 Abandoned/E) instead of diverging display-score thresholds.
- Extension: Table sorting now keeps detail rows paired with their parent package row. Previously, sorting would break the pairing and cluster orphaned detail rows together.
- Extension: Table filtering now correctly hides detail rows when their parent row is filtered out, preventing orphaned expanded cards from remaining visible.
12.0.3 #
Upgrade plans skip git, path, and SDK deps that can't be bumped, surface real error reasons instead of "pub get failed", and keep going to the next package when one step fails. — log
Fixed #
- Extension: Upgrade plan no longer includes git, path, or SDK dependencies that cannot be upgraded via version constraint bump. Previously these would appear in the plan and immediately fail with an unhelpful "pub get failed" message.
- Extension: Upgrade report now shows the actual error reason (e.g. version conflict details) instead of just "pub get failed" or "flutter test failed".
- Extension: Upgrade plan continues to the next package after a step failure instead of halting the entire plan. Each failed step rolls back independently so subsequent packages still get attempted.
12.0.2 #
Size Distribution chart splits unique vs shared transitives (with an "Exclude shared" toggle) so you can spot when a package's apparent weight is really deps you already carry. — log
Added #
- Extension: Size Distribution chart now separates transitive dependencies into distinct "Unique transitives" and "Shared transitives" segments instead of burying them in a single "Other" bucket. Unique transitives are the real cost of adding a package — shared transitives are already pulled in by other direct deps. A new "Exclude shared" checkbox hides shared transitive segments from both charts and the table, recalculating percentages for the remaining packages. This makes inflated size reports (e.g. a 63 MB package whose weight is entirely from a dep you already carry) immediately visible.
- Extension: Report renamed from "Package Vibrancy Report" to "Saropa Package Vibrancy" with the extension version shown as dimmed text next to the title.
- Extension: Animated radial gauge in the report header (floating top-right) showing the overall project health score on a color-coded 270-degree arc that fills on load.
- Extension: Letter grade badges (A through F) in the Category column, synced with the extension's category dictionary (A=Vibrant, B=Stable, C=Outdated, E=Abandoned, F=End-of-Life). Displayed as color-coded pill badges alongside the category label.
- Extension: Expandable detail cards — click any row (or press Enter with keyboard focus) to reveal an inline card with score breakdown, vulnerability list, file references, transitive dependency cloud, and external links. Collapse with a second click or Escape.
- Extension: Keyboard navigation in the report table — arrow keys (or j/k) move a visible focus highlight between rows, Enter/Space toggles expansion, Escape collapses all.
- Extension: New "Deps" column showing transitive dependency count per package with a tree icon. Shared dependencies are highlighted with a badge, and a tooltip lists all transitives with shared ones marked.
- Extension: Detail card dependency cloud highlights shared transitive deps in bold with a "shared" badge, so blast-radius of package removal is immediately visible.
12.0.1 #
New users get a prominent "Set Up Project" banner in the Overview sidebar (and an activation toast) when saropa_lints isn't yet in pubspec.yaml, so the setup action is one click away. — log
Changed #
- Extension: Prominent "Set Up Project" banner at the top of the Overview sidebar when
saropa_lintsis not yet inpubspec.yaml— new users no longer have to hunt for the setup action - Extension: Auto-detect notification on activation when a Dart project lacks
saropa_lints, offering one-click setup directly from the toast
12.0.0 #
Rolled back from analyzer 12 to analyzer 11 — analyzer 12 requires meta ^1.18.0 but Flutter stable (3.41.6 / Dart 3.11.4) pins meta to 1.17.0, which made saropa_lints >=10.3.0 unresolvable for every Flutter project on stable. The pub solver would reject the package outright with a meta version conflict. This downgrade restores compatibility with Flutter stable while keeping all 2134 rules and 254 quick fixes intact. — log
Fixed #
- Critical: Downgrade
analyzerfrom^12.0.0to>=9.0.0 <12.0.0— analyzer 12 requiresmeta ^1.18.0which conflicts with Flutter stable's pinnedmeta 1.17.0, making saropa_lints unresolvable for all Flutter consumers (seebugs/infra_meta_pin_flutter_incompatible.md) - Downgrade
analyzer_pluginfrom^0.14.7to>=0.11.0 <0.14.7for analyzer 11 compatibility - Add
ClassBodyMembersCompatextension to bridge analyzer 11's sealedClassBody(where.membersis only onBlockClassBody, not the base type)
11.1.0 #
Ten new quick fixes — click the lightbulb and let the IDE rewrite late, abstract final, unawaited(), toString(), and more for you. — log
Added #
- Quick fix:
unnecessary_library_name— remove the library name, leaving barelibrary; - Quick fix:
avoid_late_for_nullable— remove thelatekeyword from nullable field/variable declarations - Quick fix:
prefer_late_final— changelatetolate finalfor single-assignment variables - Quick fix:
prefer_abstract_final_static_class— addabstract finalmodifiers to static-only classes - Quick fix:
avoid_async_call_in_sync_function— wrap unhandled Future call withunawaited() - Quick fix:
avoid_default_tostring— generate atoString()override listing all instance fields - Quick fix:
missing_use_result_annotation— add@useResultannotation before builder/factory methods - Quick fix:
avoid_unnecessary_local_late— removelatefrom immediately-initialized local variables - Quick fix:
avoid_unnecessary_late_fields— removelatefrom constructor-assigned fields - Quick fix:
avoid_positional_boolean_parameters— convert positional bool parameter to required named
Changed #
- Quick fix:
RemoveLateKeywordFixnow handlesVariableDeclarationStatementnodes (used byavoid_unnecessary_local_late)
Maintenance
- **Security:** Fix CVE in transitive dependency `serialize-javascript` (RCE via RegExp.flags and Date.toISOString) by adding npm `overrides` to pin `>=7.0.5`11.0.0 #
A major extension UX upgrade featuring a new searchable command catalog sidebar, embedded health dashboards, rich package details with logos and README images, unique vs. shared dependency breakdowns, and workspace-wide diagnostic suppression tracking. — log
Added #
- Extension: Commands sidebar section — a searchable, always-visible index of every extension command as the first sidebar section. Includes recent command history and one-click execution. The full editor-tab catalog remains available via the "Open full catalog" link.
- Extension: Overview now embeds Health Summary, Next Steps, and Riskiest Files groups directly — users see violation breakdowns, prioritized actions, and risky files without enabling standalone sidebar sections. Clicking items filters the Violations view. Standalone sections remain available for users who prefer dedicated views.
- Extension: Package Details sidebar section now defaults to visible — it only appears when a Vibrancy scan has results (gated by the existing
whenclause), so no clutter for users who haven't scanned. - Extension: Vibrancy scoring now includes an ecosystem adoption bonus based on reverse dependency count — how many published packages on pub.dev depend on a given package. Packages with dependents get a score boost (up to +10 points on a logarithmic curve); packages with zero dependents are unaffected (bonus-only, no penalty). The count is displayed in the Community group of the tree view, sidebar detail, and full detail panel with a clickable link to the pub.dev search results.
- Extension: Package detail panel and sidebar now show package description (truncated with "read more" link), topic badges linking to pub.dev topic search, likes count in the Community section, direct dependencies as clickable chips, and a Documentation link to the pub.dev API reference.
- Extension: Package detail panel and sidebar now show the package logo (first non-badge image from README) in the header and a README Images gallery section. Both are lazy-loaded from the GitHub API when the detail panel opens. HTTP-only images are filtered out to prevent silent CSP failures.
- Extension: Package detail and sidebar CSP updated to allow HTTPS images for logo and README screenshots.
- Plugin: Suppression tracking — every diagnostic silenced by
// ignore:,// ignore_for_file:, or baseline is now recorded as a fullSuppressionRecord(rule, file, line, kind). Records are included in batch data for cross-isolate merging, deduplicated with normalized paths, and exported inviolations.jsonwithbyKind,byRule, andbyFilebreakdowns. Counts appear in the console summary log and the extension Overview tree. Foundation for Discussion #56 suppression audit trail.
Changed #
- Extension: Size Distribution chart in the vibrancy report now has an "Include transitives" checkbox. Unchecking it hides transitive packages from both the bar chart and donut chart, recalculating percentages for direct dependencies only. Helps identify whether a package's apparent size is real or inflated by shared transitive weight.
- Extension: Package Vibrancy tree now shows unique vs shared transitive dependency breakdown in the Dependencies group. Shared transitives are already in the project via other direct deps — only unique transitives represent added weight. Package rows show a compact
N% sharedindicator so misleading size reports (e.g. a 63MB package whose weight is entirely from a dep you already carry) are immediately visible. - Extension: Package detail sidebar webview now includes a Dependencies section with a visual unique/shared bar, counts, and shared dependency names.
- Extension: Package Vibrancy tree row inline icons replaced: removed redundant go-to-file icon (row click already navigates) and added Copy as JSON (
$(clippy)) and Focus Details ($(preview)) inline actions. - Extension: File Risk section moved above Violations in the sidebar so it acts as a natural file selector before the detail view.
- Extension: File Risk summary replaced the confusing "Top N files have X% of critical issues" label with a flat breakdown: file count, critical, high, and other counts.
- Extension: Clicking a file in the File Risk tree now opens the file in the editor (in addition to filtering the Violations view).
- Extension: File Risk tree now has a Copy All toolbar button (clipboard icon) for copying the full tree as JSON.
- Extension: File Risk file items now have right-click context menu actions: Show Violations for File, Hide File, Copy Path, and Copy as JSON.
- Extension: File Risk summary node is now clickable — opens all violations in the Violations view.
- Extension: File Risk tree now respects view-level suppressions from the Violations view (hidden folders, files, rules, severities, and impacts).
- Extension: File Risk tree shows a "Scanned Xd ago" timestamp node at the bottom. When scan data is older than 24 hours, the node shows a warning icon and clicking it runs analysis to refresh.
- Extension: All tree views (Violations, File Risk, Summary, Security Posture, Suggestions) now respect rules disabled in
analysis_options.yaml(diagnostics:section) andanalysis_options_custom.yaml(RULE OVERRIDESsection). Violations for disabled rules are automatically hidden even whenviolations.jsonis stale. - Extension: Right-clicking a violation in the Violations tree now offers "Disable rule(s)" to persistently disable the rule via
analysis_options_custom.yaml, in addition to the existing view-level "Hide Rule" suppression. - Extension: Package Vibrancy tree items now show the category label in parentheses (e.g.
(Stable),(Outdated)) instead of the verbose3/10 — Outdated — 1 problemformat, consistent with the vibrancy report terminology. The full score remains in the hover tooltip and detail views. - Extension: Group node counts now use brackets (e.g.
Dependencies [5]) instead of parentheses for visual distinction from the grade. - Extension: "Source" node renamed to "Source Code" with a shorter description (e.g.
2.5k lines, 18 files). Full detail shown in tooltip. Double-clicking opens the package's local source folder. - Extension: Hover tooltip in pubspec.yaml now includes all information from the detail panel — version, community stats, size, file usages, alerts, vulnerabilities, platforms, alternatives, and action items. Footer links include pub.dev, Changelog, Versions, Repository, Open Issues, and Report Issue.
- Extension: Links in the package detail panel and sidebar detail view now render as underlined hyperlinks for discoverability. Added direct links to Changelog, Versions, Open Issues, and Report Issue alongside existing pub.dev and Repository links.
Fixed #
- Extension: Violations tree file items no longer expand to empty.
getChildren()re-readviolations.jsonon every expansion — if the file was temporarily unavailable (write lock during scan, concurrent rewrite), the early-return guards returned[]before reaching the file-item handler. File and group nodes now resolve from their embedded data before any disk read, so already-loaded children survive a transient I/O hiccup. - Extension: Pubspec validation no longer shows duplicate diagnostics on startup.
onDidOpenTextDocumentfires retroactively for already-loaded documents, and thevisibleTextEditorsloop covered them again — deduplicating the initial sync preventsupdate()from running twice for the same file. - Extension:
stale-overrideno longer false-positives on overrides that resolve SDK-pinned transitive conflicts (e.g.meta: 1.18.0whenflutter_testpins1.17.0butanalyzer ^12requires^1.18.0). The override analyzer now compares the overridden version against the dep-graph resolved version — if they differ, the override is classified as active.
Maintenance
- Consolidated 7 example fixture packages into 2 (
example/andexample_packages/). Mergedexample_async,example_core,example_platforms,example_style, andexample_widgetsinto the mainexample/directory. Onlyexample_packagesremains separate (it requires theblocdependency). Reduces pubspec/lockfile/analysis_options maintenance from 7 projects to 2.
10.12.2 #
Pubspec inline suppression comments, l10n.yaml false-positive fix, and scan logger cleanup. — log
Added #
- Extension: Inline suppression for pubspec validation rules via
# saropa_lints:ignore <rule_code>[, ...]comments. Place the directive on the line above (or inline after) a flagged entry to suppress specific diagnostics without disabling the rule globally. All 11 pubspec rules support suppression automatically.
Fixed #
- Extension:
prefer_l10n_yaml_configno longer fires whenl10n.yamlalready exists alongsidepubspec.yaml. Flutter tooling requiresgenerate: truein pubspec even with a dedicated l10n config file — flagging it was a false positive. - Extension: Package Vibrancy scan logger no longer creates a separate log file per scan. Scans are debounced (5 s after last
pubspec.lockchange), logs append to one file per day, and identical-result scans are skipped entirely.
10.12.1 #
10.12.0 #
False-positive fixes across hardcoded config, dependency ordering, adoption gate, and pubspec diagnostics; plus a help hub, comment-preserving sort, and command catalog refresh. — log
Fixed #
avoid_hardcoded_config(v5): No longer reports URL/key-like string literals used as initializers for top-levelconstdeclarations orstatic constclass fields. Those are the usual single-source-of-truth pattern; mutablestatic final/ locals still warn.dependencies_ordering(extension): No longer flags SDK dependencies (flutter,flutter_localizations,flutter_test,integration_test) as out of alphabetical order when they appear before pub-hosted packages. SDK deps are now exempt from the alphabetical sort; only pub-hosted entries are checked.- Adoption Gate (extension): No longer shows false "Discontinued" badge on SDK dependencies (
flutter,flutter_test,flutter_localizations,integration_test, etc.). SDK packages are not hosted on pub.dev; looking them up produced misleading warnings because the pub.devflutterplaceholder is marked discontinued. Also fixed badge placement:findPackageLinenow only matches within dependency sections, so badges no longer appear onenvironment:constraint lines. prefer_publish_to_none(extension): No longer flags packages that havetopics:,homepage:, orrepository:fields — these are pub.dev publication signals, so suggestingpublish_to: nonewas a false positive on intentionally published packages.- Pubspec diagnostics (extension): All 11 pubspec.yaml validation messages now include the
[saropa_lints]prefix, matching the convention used by the Dart-side lint rules. isLintPluginSourceguard (infra): The per-file guard that prevents rules from firing on their own source code was broken in the native analyzer model — it ran once at registration time, not per-file. Moved the check into_shouldSkipCurrentFile()so it evaluates per-file and removed the 43 dead per-rule guards across 12 rule files. Fixes 8 false positives fromavoid_ios_in_app_browser_for_authon its own OAuth URL pattern definitions, plus potential false positives in all other affected rule files.
Added (Extension) #
- Help hub: New “Saropa Lints: Help” command (
saropaLints.openHelpHub) opens a quick pick for Getting Started, About, Browse All Commands, and pub.dev. Overview intro links are grouped under a permanent collapsible Help & resources tree section; the title bar shows only the Command Catalog icon (help is in the tree). Violations always shows a Help & resources row at the top when the tree has content, plus both Help and Command Catalog icons in the title bar.
Fixed (Extension) #
- Pubspec sorter: Comments that precede a dependency entry (description, changelog URL, version-pin notes) now travel with the entry during sorting instead of being stripped. Trailing decorative comment blocks (section dividers) at the end of a section are also preserved. Previously, running "Sort Dependencies" on a richly commented pubspec would silently delete all comments.
Changed (Extension) #
- Command catalog: Sidebar title actions on Overview and Violations open the catalog; Codicons load in the webview; recent command runs are stored for one-click replay with a clear control; UI refresh (hero header, cards, command IDs). Toolbar trim: fewer Package Vibrancy and Violations title-bar entries (secondary actions remain in the command palette and catalog). Context menu: removed “Log package details” from package rows. Catalog UX: categories ordered setup → analysis → violations → rules → security → reporting → vibrancy → …; entries sorted A–Z within each section; search indexes title, description, and command id (including spaced tokens); responsive layout for narrow panes.
Maintenance
- SDK_PACKAGES (extension): Consolidated three duplicate
SDK_PACKAGESsets (annotate-command, unused-detector, pubspec-sorter) into a single shared constant atsdk-packages.ts. Added missingintegration_testandflutter_driverentries to the pubspec-sorter set.
10.11.0 #
New graph command for import visualization, a searchable command catalog in the extension, eleven pubspec validation diagnostics with quick fixes, and a batch of bug fixes. — log
Added #
- cross_file graph command: New
dart run saropa_lints:cross_file graphcommand exports the import graph in DOT format for Graphviz visualization. Use--output-dirto control whereimport_graph.dotis written.
Added (Extension) #
- Command catalog webview: New "Saropa Lints: Browse All Commands" command opens a searchable, categorized catalog of every extension command (117 commands across 13 categories). Features instant text search, collapsible category sections, click-to-execute, and a toggle to reveal context-menu-only commands. Accessible from the command palette, welcome views, and the getting-started walkthrough.
- Enablement audit: Seven copy-as-JSON commands (
Copy Violations as JSON,Copy Config as JSON, etc.) previously hidden from the command palette are now visible when a Dart project is open. Commands have unique titles so they are distinguishable in the palette. - Walkthrough expansion: Three new getting-started walkthrough steps — Package Health (dependency scanning and SBOM), TODOs & Hacks (workspace-wide marker scanning), and Browse All Commands (command catalog).
- Welcome view links: Both welcome views (non-Dart project intro and no-analysis-yet prompt) now include a "Browse All Commands" link to the command catalog.
- Pubspec validation diagnostics: Eleven inline checks on
pubspec.yaml, shown in the Problems panel and as editor squiggles:avoid_any_version(Warning): Flagsanyversion constraints in dependenciesdependencies_ordering(Info): Flags unsorted dependency listsprefer_caret_version_syntax(Info): Flags bare version pins (1.2.3) — suggests caret syntax (^1.2.3)avoid_dependency_overrides(Warning): Flagsdependency_overridesentries without an explanatory commentprefer_publish_to_none(Info): Flags pubspec files missingpublish_to: nonefieldprefer_pinned_version_syntax(Info): Stylistic opposite ofprefer_caret_version_syntax— flags caret ranges, prefers exact pins (opt-in)pubspec_ordering(Info): Flags top-level fields not in recommended order (name, description, version, ...)newline_before_pubspec_entry(Info): Flags top-level sections without a preceding blank lineprefer_commenting_pubspec_ignores(Info): Flagsignored_advisoriesentries without an explanatory commentadd_resolution_workspace(Info): Flags workspace roots missingresolution: workspacefieldprefer_l10n_yaml_config(Info): Flags inlinegenerate: trueunder flutter — suggestsl10n.yaml
prefer_pinned_version_syntaxandprefer_caret_version_syntaxare mutually exclusive stylistic rules — controlled viasaropaLints.pubspecValidation.preferPinnedVersionssetting (default: off = caret preferred). Changes take effect immediately on open pubspec files.- Quick-fix code actions for 5 pubspec diagnostics:
prefer_caret_version_syntax(add^),prefer_pinned_version_syntax(remove^),prefer_publish_to_none(insert field),newline_before_pubspec_entry(insert blank line),add_resolution_workspace(insert field). Available from the lightbulb menu andCtrl+.. - Diagnostics update live as you edit pubspec.yaml (300ms debounce). SDK/path/git dependencies and
dependency_overridesare handled correctly. - Package vibrancy sort spacing: Sort Dependencies now inserts blank lines between packages for readability. Related packages that share a common name prefix (e.g.
drift,drift_flutter,drift_dev) are kept together without a separator. SDK packages are always separated from non-SDK packages.
Fixed (Extension) #
- Duplicate annotation comments: The annotate-packages feature could leave duplicate description comments above a dependency (e.g. two identical
# A composable, multi-platform...lines) when re-run on a pubspec that already had annotations from a prior run. The scanner now removes all consecutive auto-description lines above a URL, not just the single closest one.
Fixed #
- Sidebar section toggles not responding: Clicking an "Off" sidebar toggle in Overview & options produced no feedback. Root cause: the
toggleSidebarSectioncommand was registered at runtime but not declared incontributes.commands, so VS Code silently ignored tree-item clicks. Added the command declaration and acommandPalettehide entry, and wrapped the handler in try/catch so config-update failures now surface as error notifications. - avoid_stream_subscription_in_field: Fixed false positive when
.listen()is inside a conditional block (if/for) and assigned to a properly-named subscription field. The parent-walk loop now stops at closure (FunctionExpression) boundaries to prevent escaping into outer scopes. Note: this also fixes false negatives where a bare.listen()inside a closure was incorrectly suppressed because an outer scope had a properly-named subscription assignment — those uncaptured subscriptions will now correctly fire the lint. - cross_file HTML reporter: Fixed string interpolation bug in index page — file counts were rendered as list objects instead of numbers.
- cross_file --exclude: The
--excludeglob flag is now applied to filter results. Previously it was parsed but silently ignored.
Maintenance
- Unified pubspec.yaml listener: Pubspec validation and SDK constraint diagnostics now share a single
registerPubspecDocListenershelper with one debounce timer (300ms), eliminating duplicate event subscriptions. Includes error boundary — a pubspec validation failure does not block SDK diagnostics. - Internal:
parseDependencySections()now accepts a pre-split lines array, eliminating a duplicatecontent.split('\n')call per validation run. - Roadmap restructure: Split deferred rules into focused documents in
plan/deferred/by barrier type (cross-file, unreliable detection, external dependencies, framework limitations, compiler diagnostics, not viable). Trimmed ROADMAP.md to actionable content only. Moved cross-file CLI design toplan/cross_file_cli_design.md. - Bug Report Guide: Added
bugs/BUG_REPORT_GUIDE.md— structured template and investigation checklist for filing lint rule bugs (false positives, false negatives, crashes, wrong fixes, performance) - Changelog Archive: Moved [9.9.0] and older logs to CHANGELOG_ARCHIVE.md
- Plan history restore: Restored 21 active plan/discussion/bug documents plus
deferred/andimplementable_only_in_plugin_extension/directories back toplan/root — these were incorrectly swept intoplan/history/by the consolidation commit. Addedplan/history/INDEX.mdas a searchable index for the 1,069 history files.
10.10.0 #
Ten new rules targeting deprecated APIs, performance traps, and migration gotchas across Dart and Flutter. — log
Added #
- prefer_isnan_over_nan_equality: Flags
x == double.nan(always false) andx != double.nan(always true) — use.isNaNinstead (IEEE 754). Includes quick fix. - prefer_code_unit_at: Flags
string.codeUnits[i]which allocates an entire List just to read one code unit — usestring.codeUnitAt(i)instead (Flutter 3.10, PR #120234). Includes quick fix. - prefer_never_over_always_throws: Flags the deprecated
@alwaysThrowsannotation frompackage:meta— useNeverreturn type instead (Dart 2.12+). - prefer_visibility_over_opacity_zero: Flags
Opacity(opacity: 0.0, ...)which inserts an unnecessary compositing layer — useVisibility(visible: false, ...)instead (Flutter 3.7, PR #112191). - avoid_platform_constructor: Flags
Platform()constructor usage deprecated in Dart 3.1 — all useful Platform members are static. - prefer_keyboard_listener_over_raw: Flags deprecated
RawKeyboardListener— useKeyboardListenerwhich handles IME and key composition correctly (Flutter 3.18). Includes quick fix. - avoid_extending_html_native_class: Flags extending native
dart:htmlclasses (HtmlElement,CanvasElement, etc.) which can no longer be subclassed (Dart 3.8 breaking change). - avoid_extending_security_context: Flags extending or implementing
SecurityContextfromdart:iowhich is nowfinal(Dart 3.5, breaking change #55786). - avoid_deprecated_pointer_arithmetic: Flags deprecated
Pointer.elementAt()fromdart:ffi— use the+operator instead (Dart 3.3). Includes quick fix. - prefer_extracting_repeated_map_lookup: Flags 3+ identical
map[key]accesses in the same function body — extract into a local variable for readability and type safety (Flutter 3.10, PR #122178).
10.9.0 #
Four new rules catching deprecated media query params, codec shorthand, a removed AppBar field, and iterable cast cleanup. — log
Added #
- prefer_iterable_cast: Flags
Iterable.castFrom(x)(andList.castFrom,Set.castFrom,Map.castFrom) and suggests the more readable.cast<T>()instance method (Flutter 3.24, PR #150185). Includes quick fix. - avoid_deprecated_use_inherited_media_query: Flags the deprecated
useInheritedMediaQueryparameter onMaterialApp,CupertinoApp, andWidgetsApp(deprecated after Flutter 3.7). The setting is ignored. Includes quick fix to remove the argument. - prefer_utf8_encode: Flags
Utf8Encoder().convert(x)and suggests the shorterutf8.encode(x)fromdart:convert(Dart 2.18 / Flutter 3.16, PR #130567). Includes quick fix. - avoid_removed_appbar_backwards_compatibility: Flags the removed
AppBar.backwardsCompatibilityparameter (removed in Flutter 3.10, PR #120618). Includes quick fix to remove the argument.
Fixed #
- avoid_global_state: Report diagnostic at declaration level instead of individual variable nodes to prevent wrong line numbers when doc comments precede the declaration
10.8.1 #
Vibrancy report polish — better empty-cell display, smarter column layouts, and clickable package names. — log
Changed #
- Vibrancy Report: Empty cells now show an em-dash with an explanatory tooltip instead of blank space (stars, published date, issues, PRs, size, license, description, and other optional columns)
- Vibrancy Report: Merged Health column into Category as a dimmed suffix, e.g. "Abandoned (1/10)"
- Vibrancy Report: Package name now opens pubspec.yaml at the dependency entry (was: pub.dev link) and shows description as tooltip
- Vibrancy Report: Published date now links to the pub.dev package page and shows the version age suffix (moved from Version column)
- Vibrancy Report: "Files" column renamed to "References" — click the count to search your workspace for that package's imports
- Vibrancy Report: Update column shows a dimmed en-dash instead of a checkmark when no update is available; all placeholder dashes are now dimmed
- Vibrancy Report: License and Description columns are now hidden by default (Description changed from icon to plain-text column)
10.8.0 #
Vibrancy report gets GitHub issue and PR counts, plus a toolbar toggle for Drift Advisor integration. — log
Added #
- Vibrancy Report: New "Issues" and "PRs" columns show open GitHub issue and pull request counts, linking directly to the repository's issues and pulls pages
- Drift Advisor: Toolbar toggle button in the Drift Advisor view —
$(plug)enables integration,$(circle-slash)disables it. No more hunting through Settings to findsaropaLints.driftAdvisor.integration.
10.7.0 #
Vibrancy health categories renamed for clarity, report gains copy-as-JSON, file usage tracking, and clickable summary cards. — log
Changed #
- Vibrancy: Renamed health categories for clarity — "Quiet" → Stable, "Legacy-Locked" → Outdated, "Stale" → Abandoned. Vibrant and End of Life are unchanged.
- Vibrancy: Raised Abandoned threshold from score <10 to score <20 so packages untouched for 4+ years with only bonus points are correctly flagged instead of escaping into Outdated
- Vibrancy Report: Overrides summary card is now clickable — filters the table to show only overridden packages
- Vibrancy Report: All table column headings now have tooltips explaining what each column represents (e.g. Published = "Date the installed version was published to pub.dev")
Added #
- Vibrancy Report: Copy-as-JSON button appears on row hover — copies a detailed JSON of all package fields and links to clipboard
- Vibrancy Report: New "Files" column shows how many source files import each package, with clickable file paths in the detail view that open at the exact import line
- Vibrancy Report: "Single-use" summary card filters to packages imported from only one file
- Vibrancy Report: Exported JSON and Markdown reports now include per-package file-usage data (file paths and line numbers)
Fixed #
- Vibrancy Report: Commented-out imports (e.g.
// import 'package:foo/foo.dart') are no longer counted as active usage for unused-package detection
Breaking #
- Vibrancy Settings:
budget.maxStalerenamed tobudget.maxAbandoned;budget.maxLegacyLockedrenamed tobudget.maxOutdated. Users who customized these settings will need to update their config. - Vibrancy Exports: JSON/Markdown export schemas use new category keys (
stable,outdated,abandonedinstead ofquiet,legacy_locked,stale) - Generated CI scripts: Previously generated CI workflows reference old threshold variable names. Regenerate after updating.
10.6.0 #
Extension UX refinements — split workspace options into Settings and Issues sections, hide "Apply fix" for unfixable violations, and auto-expand violations tree on programmatic navigation. — log
Changed #
- Extension: Overview sidebar splits the former "Workspace options" section into two focused sections: Settings (lint integration, tier, detected packages, config actions) and Issues (triage groups by violation count); Issues hides when no analysis data exists
- Extension: "Apply fix" context menu item is now hidden for violations without a quick fix, instead of showing a dead-end "No quick fix available" message
- Extension: Violations tree now auto-expands all levels when navigated to from settings, dashboard links, summary counts, or triage groups
Fixed #
- Extension:
rulesWithFixesfromviolations.jsonwas not extracted, causing all violations to appear fixable regardless of actual fix availability
10.5.0 #
Replacement complexity metric — analyzes local pub cache to estimate feasibility of inlining, forking, or replacing each dependency; removed inline vibrancy summary diagnostic. — log
Added #
- (Extension) Package Vibrancy: replacement complexity metric — analyzes local pub cache to count source lines in each dependency's
lib/directory and classifies how feasible it would be to inline, fork, or replace (trivial / small / moderate / large / native). Shown in Size tree group, detail sidebar, and CodeLens for stale/end-of-life packages with feasible migration
Changed #
- (Extension) Removed the
vibrancy-summaryinline diagnostic frompubspec.yaml— the Package Vibrancy sidebar and report already surface this information. TheinlineDiagnosticssetting no longer offers a"summary"mode; the default is now"critical"(end-of-life packages only)
10.4.1 and Earlier #
Looking for older changes? See CHANGELOG_ARCHIVE.md for versions 0.1.0 through 10.4.1.