saropa_dart_utils 1.1.6
saropa_dart_utils: ^1.1.6 copied to clipboard
280+ extension methods and utilities for Flutter/Dart. Null-safe string, DateTime, List, Map, and number operations that eliminate boilerplate.
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
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
pub.dev - saropa_dart_utils
Published version: See field version in pubspec.yaml
Older versions: Entries for 0.5.9 and earlier live in CHANGELOG_HISTORY.md.
1.1.6 #
Fixed #
- Release fix:
dart pub publishfailed validation (exit 65), so 1.1.5 never reached pub.dev.test/async/debounce_utils_test.dartandtest/async/heartbeat_utils_test.dartimportpackage:fake_async/fake_async.dart, butfake_asyncwas only available transitively (viaflutter_test) and was not declared inpubspec.yaml. pub.dev rejects publishing a package whose sources import an undeclared library. Addedfake_async: ^1.3.3todev_dependencies(matching the versionflutter_testresolves). Nolib/changes — this republishes the 1.1.5 content under 1.1.6.
1.1.5 #
Fixed #
These five correctness bugs were surfaced by the new unit tests (see Maintenance → Tests) and are now fixed and covered by those tests:
damerauLevenshteinDistancescored an adjacent transposition as 2 instead of 1. The rolling-row optimization read the transposition term fromprevRow[j-2](rowi-1), but the OSA recurrence requiresd[i-2][j-2]— two rolling rows are insufficient. Now uses three rolling rows; e.g.'ca'→'ac'and'abc'→'acb'correctly return 1.singleValueCachere-ran its compute on every call when the result wasnull.cached ??= compute()never storesnull; replaced with an explicitcomputedflag so anull-returning compute runs exactly once, as documented. (memoize1was already correct viaputIfAbsent.)AsyncBarrierUtils.signal()threwBad state: Future already completedwhen called more thancounttimes. Added anisCompletedguard so extra signals are a no-op.detectCsvDialect('')(and any tab-free first line) returned a tab delimiter instead of the documented comma, because thetabs >= commastie-break picks tab on0 >= 0. Tab is now chosen only when tabs are actually present.SearchQueryParserUtils.parseSearchQuerykept the leading-in a negated word's text when it preceded a quoted phrase. The pre-phrase branch now strips-like the trailing-words branch; the negation is captured inisNegated.
Maintenance
Static analysis
- Resolved all 149
dart analyzeINFO findings — the analyzer now reports 0 issues acrosslib/andtest/. Real improvements were made where they help the code: single-statement block bodies converted to arrow expressions; never-reassigned locals madefinal; double-quoted literals switched to single quotes; explicit null checks replaced with null-aware calls and??; consecutive same-target calls converted to cascades; a parameter reassignment replaced with a local; commented-out code removed; a curly apostrophe corrected to a straight one in dartdoc; one-shotaddAllof a literal converted to a spread. False positives were documented rather than worked around destructively: anaddAllaccumulating across a loop is kept (a spread rebuild each pass would be O(n²));Error.throwWithStackTracereturnsNever, so its flagged "ignored return value" is a non-issue; integer rolling-hash arithmetic was mis-flagged as string concatenation;raceFirstkeeps its.then/.catchErrorchain because awaiting would serialize its deliberately-concurrent producers. Every remaining// ignore:now carries a-- rationale, and dead// ignore: require_ios_deployment_target_consistencydirectives (that rule is disabled project-wide) were removed. - Maintainer note:
saropa_lintsplugin rules require thesaropa_lints/<rule>prefix in// ignore:directives to take effect — bare rule names do not reliably suppress plugin lints. Core Dart lints (e.g.non_constant_identifier_names) stay unprefixed. - Disabled the stylistic
move_variable_closer_to_its_usagelint and removed its ~37 inline suppressions. The rule fired on this library's deliberate pattern of declaring loop accumulators and method-scope setup variables up front, and it counts comment lines toward the declaration-to-use distance (so the explanatory comments added below tripped it further). Rather than carry ~37// ignore:directives for one v7 stylistic rule, it is nowfalseinanalysis_options.yamland the redundant suppressions were deleted.dart analyzestays at 0.
Documentation
- Added dartdoc to every public member that previously lacked one —
public_member_api_docsreports 0 across all oflib/(410 members documented). Coverage spans every package directory: extensions, top-level utility functions, named/unnamed/factory constructors, getters, fields, typedefs, and enum values. Non-trivial public functions received fencedExample:blocks; getters, constructors, and fields received concise purpose-stating one-liners that document edge cases, nullability, and malformed-input behavior rather than restating the member name. Docs only — no code, signature, or logic changes. Thepublic_member_api_docslint was enabled temporarily to find and verify the complete set, then returned to its priorfalsesetting (a follow-up may keep it enabled to lock in coverage). - Documented recursion-depth limits on the public deep-structure utilities.
deepEquals,deepMerge,deepCopyMap/deepCopyList,flattenKeys,removeKeys,canonicalizeJson,flattenDeep, andsimpleHashrecurse to their input's nesting depth; their dartdoc now warns against untrusted, arbitrarily-deep input (stack-exhaustion risk).dfsnotes thatmaxDepthbounds the recursion, andflattenHierarchynotes it assumes an acyclic parent graph (a cycle would recurse without bound). No behavior changed — these are honest caveats, not guards. The audit's other "possible recursion" flags were reviewed and are either correct, bounded-by-design algorithms (union-find with path compression, trie, glob, Douglas-Peucker) or false positives (e.g.clear()/add()calling same-named methods on a field,UrlExtensions.tryParsedelegating toUri.tryParse). - Added explanatory inline (
//) comments to non-obvious logic in ~40 genuinely-complex methods acrosslib/(string, parsing, num, collections, stats, graph, datetime, map, validation, uuid). Each comment explains a rationale, invariant, edge case, or spec rule a reader cannot infer from the code — for example: the OR-as-AND term handling inparseSearchQuery, the void-element tag stack insafeHtmlExcerpt, the grapheme-vs-code-unit word break inwordWrap, and the LCS DP backtrack/tie-break in_myers; the RFC 4180 quote rule inparseCsvLine, the Luhn doubling shortcut, the ISBN-10 positional weights, the semver pre-release precedence, and the percentile "type 7" interpolation; the Levenshtein two-row space optimization and transposition guard, Kahn's-algorithm topological order with cycle detection, and the Douglas-Peucker keep/discard recurrence; ISO-8601 week anchoring inparseIsoWeekString, the path-safety depth-counter invariant, and the RFC 4122 version/variant bit-twiddling ingenerateUuidV4. Comments only — no code, signature, or logic changes. Methods that were already self-explanatory (e.g.breakLongWords,prettyPrint,semver.parse,jsonDiffShallow,bucketAggregate) were deliberately left uncommented rather than padded with filler.
Tests
- Added unit-test coverage for previously-untested public API in
lib/stats/,lib/num/, andlib/niche/. Created 16 newtest/stats/files (the directory had no tests at all) covering bucketed aggregates, confidence intervals, Pearson correlation, z-score/min-max normalization, one-hot/bucketize encoding, funnel conversion, linear regression, log/exp transforms, metric roll-ups, moving averages, MAD outliers, percentile rank, quantile summaries, retention-by-day, robust stats (median/MAD/trimmed mean), and stratified/systematic sampling. Addedtest/num/num_locale_utils_test.dartandtest/num/num_more_extensions_test.dart, and extended existing num tests with the previously-uncoveredminOf/maxOf/safeDividefree functions,floorToMultiple/ceilToMultiple,count,isInRangeInclusive, andArgumentError/empty-input edge cases. Added 7 dedicatedtest/niche/files (checksum, color luminance/contrast, hash, name, natural sort, niche-more byte/hex/mask helpers, pad/format, random string, string diff). Floating-point assertions usecloseTowith hand-computed expected values; all assertions pin actual expected values. Full run:flutter test test/stats/ test/num/ test/niche/→ 416 tests, all passing, no skips. Nolib/oranalysis_options.yamlchanges. - Added unit-test coverage for previously-untested public API in
lib/parsing/andlib/validation/. Created thetest/validation/directory (it had no tests at all) with 13 files and added 21 dedicatedtest/parsing/files alongside the existing smoke-test file. Coverage pins exact return values for every public function, method, getter, and constructor across both directories — parsers/validators are tested with both valid and malformed inputs and the exact result for bad data (e.g. ISBN-10/13 valid + altered-check-digit, Luhn valid + tampered, IPv4/CIDR membership, JWT structure + payload decode, semver parse/compareTo precedence, varint encode/decode round trips, size parse/format boundaries, password strength bands, path-traversal safety). One bug surfaced and was fixed (see Fixed, above):detectCsvDialect('')returned a tab delimiter though its dartdoc specifies comma; the test now asserts the corrected comma behavior. Full run:flutter test test/validation/ test/parsing/→ 456 passing, 1 skipped, 0 failing. Nolib/oranalysis_options.yamlchanges. - Added unit-test coverage for previously-untested public API in
lib/datetime/,lib/list/,lib/map/,lib/url/,lib/uuid/,lib/testing/, andlib/base64/. Created 22 new test files for the source files that had no test importing them: datetime arithmetic/calendar/comparison/more/timezone extensions, the injectable clock, period split, relative-date bucket, time rounding, and timebox utilities; list default-empty/lower/seeded-shuffle/top-K extensions; map "more" extensions; URL path-more/build/encode utilities; UUID v4 generation; debug/testing helpers; and both gzip codec variants (dart:ioround-trip and the always-null stub). Assertions pin exact values — explicitDateTime(y, m, d)construction (leap day, month-end, year-boundary) with noDateTime.now()in assertions, UTC instants for the timezone-offset string, and structural checks (format, version/variant nibbles, uniqueness) for the random UUID. Two real findings: (1)timeboxleaks an unhandled async error on timeout and whenfnthrows — the awaited future resolves correctly but a second error escapes to the zone, so those two tests areskipped withpossible bug:notes; (2)MapFromIterableExtension.toMapWithputs its K/V on the extension rather than the method, so the result is alwaysMap<dynamic, dynamic>at the call site (documented in-test; values are correct). Documented-behavior tests pin actual outputs where Dart APIs surprise:ListTopKExtensions.topK(k)returns the full list UNSORTED whenk >= length,prettyPrintgives top-level map entries no leading pad, andbuildUri/stripFragmentrender a trailing#fromUri.replace(fragment: '')while correctly clearing the fragment content. Skipped (nothing testable):lib/html/html_entity_data.dart(pure const data) andlib/base64/,lib/uuid/,lib/html/files already covered. Full run:flutter test test/datetime/ test/list/ test/map/ test/url/ test/uuid/ test/testing/ test/base64/ test/html/→ 1742 passing, 2 skipped, 0 failing. Nolib/oranalysis_options.yamlchanges. - Added unit-test coverage for all 39 source files in
lib/collections/(thetest/collections/directory had no tests at all). Created 39 new test files — one per source file — pinning exact return values for every public function, method, getter, and constructor: graph/DP algorithms (disjoint-set, knapsack 0/1 with reconstruction, LIS/LCS-substring, weighted/greedy interval scheduling, set cover), streaming/online structures (ring buffer overwrite semantics, reservoir + seeded shuffle with fixedRandomfor determinism, online Welford mean/variance, stream quantile, Bloom filter no-false-negatives, dedup-with-expiry), and tabular/collection utilities (bimap, multiset union/intersection/difference, trie, pivot/unpivot, columnar↔row conversion, histogram fixed/quantile bins, sliding-window aggregates, top-K by key, n-way merge, rolling hash, time bucketing, window functions). Edge cases covered empty/single/duplicate inputs, boundary capacities, and Unicode where relevant. One bug was found and fixed (see Fixed, above):damerauLevenshteinDistancescored a pure adjacent transposition ("ab"→"ba","ca"→"ac","abc"→"acb") as 2 instead of 1 (two rolling rows are insufficient for OSA — it needs three); it now uses three rolling rows and those transposition tests pass. Knapsack/empty-result assertions destructure the returned record because a record holding aListdoes not compare structurally with==. Full run:flutter test test/collections/→ 367 passing, 3 skipped, 0 failing;flutter analyze test/collections/clean. Nolib/oranalysis_options.yamlchanges. - Added unit-test coverage for all 15 source files in
lib/graph/(thetest/graph/directory had no tests at all) and the 13 previously-untested source files inlib/iterable/. Created 15 newtest/graph/files and 13 newtest/iterable/files, pinning exact return values for every public function, method, getter, and constructor. Graph coverage builds small concrete graphs and asserts exact algorithm results: topological sort of a known DAG (and cycle/self-loop detection returning null), BFS/DFS visit order + per-node depths +maxDepthcapping, connected components of disjoint pairs, A*/Dijkstra/Floyd–Warshall/critical-path shortest- and longest-path distances and predecessor chains, Kruskal MST edge selection + cost + forest handling, bipartite 2-coloring (odd cycle rejected, even cycle accepted), Douglas–Peucker keep/discard by epsilon, LCA/tree-depths over an explicit parent array, hierarchy flatten/build, graph edge diff, and the DAG scheduler's priority reordering. Iterable coverage pins exact resulting collections for cartesian product, three-way diff, first/last-where-or-else, deep flatten by depth, group-by-transform, indexed map/fold, min/max-by (with first-on-tie), the "more" extensions (take/drop-last, replace, cycle, pad, unzip, segment, consecutive pairs, arg-min/max, all-equal, count-by, scan), all-pairs, sort-by-then-by, split-at/-first-where, symmetric difference, and theOccurrencevalue class (==/hashCode/toString/map-key). Skipped (nothing newly testable): the 4lib/iterable/files already covered by existing tests (iterable_extensions,iterable_list_ops_extensions,comparable_iterable_extensions,run_length_utils) anditerable_flatten_extensions.flatten()(already exercised via the existingiterable_extensionstest). No bugs found. Full run:flutter test test/graph/ test/iterable/→ 349 passing, 0 skipped, 0 failing;dart analyze test/graph/ test/iterable/clean. Nolib/oranalysis_options.yamlchanges. - Added unit-test coverage for the 36 previously-untested source files in
lib/string/. Created 36 newtest/string/files — one per source file that had no test importing it — pinning exact return values for every public function, method, getter, extension method, and constructor. Coverage spans text utilities (acronym/code-block/URL/curly-brace extractors, email-reply-quote stripper, HTML sanitizer/safe-excerpt, Markdown→plain and snippet, sentence/word tokenizer, smart excerpt-around-query, near-duplicate clustering, sensitive-data scrubber, slug deduper, spelling-tolerant key lookup, human-name parser, search-query parser, search index, template engine, text chunker/fingerprint/normalize-pipeline/similarity, fuzzy search, did-you-mean), the diff stack (Myers line diff with merged ops, edit-script apply with conflict detection, ANSI/HTML/plain unified-diff renderer), n-gram generators, and the value classes (BetweenResult,FuzzySearchUtils,QueryTerm,HumanNameParserUtils,UrlExtractUtils,SensitiveScrubUtils,DiffOp/DiffOpKind,ApplyPatch*,SearchIndexUtils) plus the case-acronym, lower, manipulation, more, text, analysis, and unicode String extensions. Assertions pin hand-computed values (cosine-similarity rounding handled withcloseTo; ANSI/non-breaking control chars built viaString.fromCharCodeto keep the source bytes clean); time-jittered helpers (obscureText,getRandomChar) assert bounds/membership;textFingerprint'shashCode-derived value is tested for determinism/empty-zero rather than a magic number. One bug was found and fixed (see Fixed, above):SearchQueryParserUtils.parseSearchQuerykept the leading-in the term text for a negated word before a quoted phrase; it now strips it consistently with the trailing-words branch and the test passes. Full run:flutter test test/string/→ 2025 passing, 1 skipped, 0 failing. Nolib/oranalysis_options.yamlchanges.
1.1.4 #
Fixed #
-
Ambiguous extension clash — renamed the
T?.toListIfNotNull()method inlib/object/nullable_more_extensions.darttotoListOrEmpty(). It collided with the older, testedMakeListExtensions.toListIfNotNull()inlib/list/make_list_extensions.dart— both extendedT?with the same method name, so any consumer importing the package's barrel hit an ambiguous-extension error. The two have different semantics:toListIfNotNull()returnsList<T>?(nullfor a null receiver);toListOrEmpty()returnsList<T>(empty list for a null receiver). The new name matches its behavior, since "if not null" wrongly implied a nullable result. -
Ambiguous extension clash —
String.truncateWithEllipsis()(BUG-002) — removed the duplicate code-unit-basedtruncateWithEllipsis(int maxLength, [String ellipsis])fromStringLowerExtensions(lib/string/string_lower_extensions.dart). It collided with the established, grapheme-awareStringExtensions.truncateWithEllipsis(int? cutoff)— bothon String, both exported from the barrel — so consumers hit an ambiguous-extension error. Beyond the clash, the two diverged silently for emoji / multi-byte input (UTF-16 code units vs grapheme clusters), so the Unicode-correct version was kept. Added an emoji grapheme regression test. -
Ambiguous extension clash —
String.escapeForRegex()(BUG-003) — removed the duplicateescapeForRegex()(and its now-unused private_regexSpecialCharsRegex) fromStringManipulationExtensions(lib/string/string_manipulation_extensions.dart). It collided with the canonical, testedStringRegexExtensions.escapeForRegex()— bothon String, both exported from the barrel. Output is identical for all input, so no behavior changes.string_extensions.dartnow re-exportsstring_regex_extensions.dartso the method stays reachable via that file unchanged.
Changed #
nullable_more_extensions.dartdocumentation and coverage — added dartdoc with examples to every public member that previously had none (whenNonNull,mapNonNull,orElse,tryCast,isType,asTypeOr,firstOfType) and added a full test file (test/object/nullable_more_extensions_test.dart, 33 cases) covering each, including null receivers, falsy-but-non-null values, type mismatches, and the empty-list / no-match paths. The file shipped with no tests and no docs in the roadmap batch.
1.1.3 - 2026-05-22 #
Fixed #
-
Publishing — declared
metaas a direct dependency (it was only transitive). The library importspackage:meta/meta.dartacross 118 files, and pub.dev rejects publishing a package that imports a library it only depends on transitively. This had silently failed every publish since 1.0.6 (the last version actually on pub.dev): the GitHub Actions workflow maskeddart pub publish's exit-65 validation error and reported the run green while pub.dev received nothing. -
Static analysis score — wrapped 52 single-statement
if/else/for/whilebodies in braces across 38 files inlib/to satisfycurly_braces_in_flow_control_structures. pub.dev's pana enforces this lint via the analysis server (which loads thesaropa_lintsplugin), butdart analyzeCLI does not load plugins, so the violations were invisible locally while docking the "Pass static analysis" score to 40/50. The rule is now also enabled explicitly inanalysis_options.yamlsodart analyzeand CI catch any recurrence before publish.
Maintenance
Tooling
scripts/publish.pypost-publish verification (v2.7) — added STEP 14, which polls pub.dev's per-version API until the new version is actually live, using the triggered workflow run's conclusion as a fast-fail signal. The script now exits non-zero and prints recovery steps when a release never reaches pub.dev, instead of declaring success the moment the tag is pushed. A workflow that reports success while pub.dev never serves the version (the exit-65 mask signature) is reported as a failure.
CI
.github/workflows/publish.yml— removed|| [ $? -eq 65 ]from the dry-run and publish steps so a validation failure fails the workflow instead of being masked as a green run.
1.1.2 #
- Version bump for publishing.
1.1.1 #
Fixed #
parseVersion— removed invalid named positional fields from record return type(int major, int minor, int patch)→(int, int, int)to fixinvalid_field_nameanalyzer error.MapExtensions.toMapStringDynamic(BUG-010) — addedthrowOnDuplicateparameter so callers can detect when two source keys collapse to the sameString(e.g.int1andString'1') instead of silently losing a value. Collision policy is now explicit:throwOnDuplicatethrowsArgumentError,ensureUniqueKeykeeps the first value, default keeps the last. Behavior is unchanged for existing callers.
Maintenance
Tooling
reports/organize_reports.py— local copy of shared report organizer script, tracked in git via.gitignorenegation pattern while keeping generated report files ignored.scripts/publish.pypre-publish quality audit (v2.5) — the audit phase now runs two additional checks before publishing and reports the top 10 of every category to the terminal (full results logged to the report file): inline code-comment density per method (flags branch/loop/variable-heavy methods that lack//explanations) and per-parameter unit test coverage (flags methods tested by fewer thanparams + 1test blocks). Analyzer findings now include the actual messages, not just counts. The post-audit prompt changed fromContinue? [y/N]to ignore / retry / abort, whereretryre-runs all checks after fixes andabort(the default) cancels.
Lint
- Lint cleanup — cleared
saropa_lintsdiagnostics across several files:string_extensions.dart—prefer_single_quotesinwrapWithinterpolation.parsing/hex_color_utils.dart,parsing/luhn_utils.dart—move_variable_closer_to_its_usage: relocated function-local consts to their use sites to tighten scope.map/map_diff_utils.dart—move_variable_closer_to_its_usage: movedremoveddeclaration to just before the second loop (its only use).map/map_extensions.dart—prefer_cascade_over_chainedon consecutiveStringBuffer.writecalls;avoid_ignoring_return_valuesanddocument_analyzer_ignore_rationaleon the recursiveremoveKeysandupdate/putIfAbsentsuppressions.iterable/occurrence.dart,string/between_result.dart,iterable/iterable_extensions.dart,list/unique_list_extensions.dart—document_analyzer_ignore_rationale: added inline rationale to existing// ignore:directives.
Verified
UuidUtils.addHyphens(BUG-017) — confirmed hex-content validation (32-char non-hex strings now returnnullinstead of producing a fake UUID) and added regression tests for non-hex, mixed-case, and punctuation inputs.
Documentation
- CHANGELOG split — moved entries for
0.5.9and earlier intoCHANGELOG_HISTORY.md, and collapsed non-user-facing items (lint, tests, refactoring, documentation, tooling) into per-versionMaintenanceblocks. String.between(BUG-029) — documented that an emptyenddelimiter is treated as "not found", so theendOptionalrules apply (returns the tail afterstartby default, empty string whenendOptional: false).String.betweenResult(BUG-021) — clarified in tests that it returns the outermost match (firststartto lastend), contrasting withbetween()which returns the first balanced pair.
Tests
List.takeSafe(BUG-023) — added tests pinning the documented non-standard default (takeSafe(0)returns the original list; opt into Darttake(0)semantics withignoreZeroOrLess: false).DateTime.weekNumber/numOfWeeks(BUG-024) — added exact ISO 8601 boundary tests (Jan 1 2010 → week 53 of 2009, Dec 31 2012 → week 1 of 2013, Jan 4 always week 1, 53-week-year detection).num.length()(BUG-030) — added tests for scientific-notation behavior at magnitudes ≥ 1e21 and theBigIntworkaround for true digit counts.date_time_range_utils_test(BUG-026) — removed a duplicate "5th Monday of February doesn't exist" test and corrected a test name that said "returns true" while assertingisFalse.- BUG-020 — verified the previously-flagged methods (
getFirstDiffChar,hasInvalidUnicode,removeInvalidUnicode,collapseMultilineString,splitCapitalizedUnicode,isVowel,pluralize,endsWithPunctuation,endsWithAny,removeSingleCharacterWords) all have dedicated test groups.
Restored
- Deleted bug reports and lint assessments recovered from git history into
plans/history/<deletion-date>/. Restored 41 files keyed by the date they were deleted:plans/history/2026.03.06/holds 25 resolved bug reports (BUG-001…BUG-034, the subset not still open inbugs/), 11 lint-rule assessments,INDEX.md,20260223_legitimate_fixes_report.md, andverify_documented_parameters_exist.md;plans/history/2026.02.22/holdsavoid_very_long_length_files.mdand an earlierverify_documented_parameters_exist.md. Files whose content survives as renamed descendants inplans/history/(e.g.avoid_duplicate_cascades,prefer_iterable_of,prefer_parentheses_with_if_null) were not duplicated.
1.1.0 #
Breaking #
DateTimeUtils.isDeviceDateMonthFirst()renamed toisDateMonthFirst({required String localeName})— removesdart:iodependency; caller now passes the locale string.
Enhanced #
- HtmlUtils entity expansion: Replaced 35-entity regex-based
replaceAlldecoder with single-pass O(1) Map-lookup scanner covering 278 HTML5 named entities (268 fromhtml_unescapev2.0.0 basic set + 10 beyond Latin-1: trade, euro, bullet, ellipsis, dashes, typographic quotes). Supports all numeric entities (decimalAand hexA), legacy no-semicolon forms per HTML5 spec, and validates Unicode scalar values including surrogate rejection. Entity data derived from Filip Hracek'shtml_unescapepackage (BSD-3-Clause). Tests expanded from 31 to 49.
Fixed #
- AsyncBarrierUtils: fixed
StateErrorwhen accessing.futureafter barrier already completed (double-complete guard). - retryWithBackoff name collision: renamed
retryWithBackoffinretry_policy_utils.darttoretryWithJitterto avoid conflict withretry_utils.dart.
Maintenance
Lint
- avoid_platform_specific_imports linter: removed
dart:iofromdate_time_utils.dart(locale parameter) andbase64_utils.dart(conditional imports for gzip). - avoid_stack_trace_in_production linter: removed
stackTracefromdev.log()calls in 7 files (retry_policy_utils,retry_utils,timeout_fallback_utils,timeout_policy_utils,timebox_exception,parse_list_utils,url_encode_utils). Error objects are still logged; stack traces are no longer exposed per OWASP M10. - ambiguous_export: resolved
AsyncActionname collision betweenasync_semaphore_utils.dartandasync_mutex_utils.dart; mutex now imports from semaphore.
Refactoring
- Typedef duplication: consolidated
AsyncProducer,FutureSupplier, andAsyncAction(allFuture<T> Function()) into singleAsyncActiontypedef inasync_semaphore_utils.dart.
1.0.8+1 #
A large expansion of the library (collections, graph, stats, validation, async, parsing, and many more string utilities), plus documentation and lint fixes.
Added #
New and expanded APIs (all exported from package:saropa_dart_utils):
- Collections (
lib/collections/): lis_utils, lcs_substring_utils, sliding_window_aggregate_utils, reservoir_sampling_utils, interval_scheduling_utils, trie_utils, disjoint_set_utils, damerau_levenshtein_utils, knapsack_utils, bloom_filter_utils, nway_merge_utils, ring_buffer_utils, multiset_utils, online_mean_variance_utils, histogram_utils, difference_array_utils, bimap_utils, kmeans_utils, weighted_interval_utils, greedy_set_cover_utils, chunk_overlap_utils, pivot_unpivot_utils, run_detection_utils, stream_quantile_utils, inverted_index_utils, top_k_heap_utils, time_bucket_utils, multi_criteria_sort_utils, columnar_view_utils, window_functions_utils, balanced_partition_utils, bin_packing_utils, prefix_frequency_utils, rolling_hash_utils, dedup_set_expiry_utils, string_pool_utils, row_column_table_utils, priority_map_utils, seeded_shuffle_utils. - Graph (
lib/graph/): graph_utils, bfs_dfs_utils, dijkstra_utils, astar_utils, connected_components_utils, line_simplify_utils, hierarchy_utils, floyd_warshall_utils, topological_sort_utils, mst_utils, critical_path_utils, bipartite_utils, tree_utils, graph_diff_utils, dag_scheduler_utils. - Stats (
lib/stats/): robust_stats_utils, moving_average_utils, data_normalization_utils, quantile_summary_utils, correlation_utils, linear_regression_utils, bucketed_aggregate_utils, confidence_interval_utils, funnel_utils, outlier_mad_utils, percentile_rank_utils, retention_utils, sampling_utils, metric_rollup_utils, log_transform_utils, feature_encoding_utils. - Validation (
lib/validation/): validation_error_utils, path_validator_utils, input_shaping_utils, guard_utils, cross_field_validation_utils, safe_temp_name_utils, password_strength_utils, pii_detector_utils, data_redaction_utils, safe_parse_utils, typed_positive_utils, ip_cidr_utils, jwt_structure_utils. - String (extensions + utils): levenshtein_utils, string_slug/mask/template/regex/wildcard/line/wrap/indent/replace_n/highlight/csv/ansi/words/key_value/split/unicode/case_acronym_extensions; glob_utils, soundex_utils; myers_diff_utils, diff_render_utils, apply_patch_utils, ngram_utils, slug_dedup_utils, fuzzy_search_utils, excerpt_utils, text_similarity_utils, sensitive_scrub_utils, text_chunk_utils, html_sanitizer_utils, tokenize_sentences_utils, markdown_plain_utils, search_query_parser_utils, code_block_extract_utils, url_extract_utils, safe_html_excerpt_utils, template_engine_utils, acronym_extract_utils, text_normalize_pipeline_utils, duplicate_doc_utils, human_name_parser_utils, search_index_utils, markdown_snippet_utils, text_fingerprint_utils, spelling_key_lookup_utils, email_quote_strip_utils, did_you_mean_utils.
- Async: debounce_utils, delay_utils, memoize_future_utils, retry_utils, sequential_async_utils, throttle_utils, timeout_fallback_utils, batch_async_utils, cancel_previous_exception (cancelPrevious + CancelPreviousException), async_semaphore_utils, async_mutex_utils, stream_buffer_utils, exponential_backoff_utils, retry_policy_utils, batch_flush_utils, circuit_breaker_utils, async_barrier_utils, timeout_policy_utils, race_cancel_utils, idempotent_async_utils, stream_window_utils, heartbeat_utils.
- Parsing: csv_parse_utils, email_validation_utils, hex_color_utils, isbn_utils, luhn_utils, parse_bool_utils, parse_list_utils, phone_normalize_utils, semver_utils, size_parse_utils, validate_non_empty_utils, version_parse_utils, version_compare_utils, parsing_more_utils, config_precedence_utils, csv_dialect_utils, parser_error_utils, canonicalize_json_utils, changelog_section_utils, json_diff_patch_utils, nested_query_parser_utils, varint_utils.
- DateTime: date_time_more_extensions, time_rounding_utils, relative_date_bucket_utils, period_split_utils, injectable_clock_utils, timebox_exception (timebox + TimeboxException) (plus existing bounds, business days, duration format/parse, relative, fiscal, week, timezone, clamp, list, overlap).
- Map: map_pick_omit_extensions, map_more_extensions (plus existing deep merge/deep/utils, default, diff, flatten, from_entries, invert, merge, nested, transform, nullable).
- List: list_lower_extensions, list_default_empty_extensions (plus existing binary search, rotate, string, nullable, of_list, make_list, unique).
- Num: num_more_extensions (plus existing math, clamp, compact_parse, format, iterable, lerp, locale, min_max, modulo, prime, factorial, range, round_multiple, safe_division, stats, utils).
- Object / pipe: pipe_compose_utils, nullable_more_extensions (plus existing assert, cast, coalesce, copy_with_defaults, default_value_extensions, identity, pipe, require, shallow_copy).
- Niche: hash_utils, string_diff_utils, checksum_utils, natural_sort_utils, uuid_v4_utils, niche_more_utils (plus color_utils, name_utils, pad_format_utils, random_string_utils).
- URL/Path: path_more_utils (plus path_extension, path_join, url_absolute, url_build, url_encode, url_extensions, url_query).
- Caching: lru_cache, memoize_sync_utils, size_limit_cache, ttl_cache.
- Regex: regex_common_utils, regex_match_utils.
- Testing: debug_utils (exported from barrel).
- Scanner tool (
tool/suggest_saropa_utils.dart): CLI to suggest saropa_dart_utils replacements (e.g.x == null || x.isEmpty→x.isNullOrEmpty). Options:[path],--help,--version. Core intool/suggest_saropa_utils_lib.dart; tests intest/tool/suggest_saropa_utils_test.dart.
Fixed #
- avoid_nullable_interpolation in
string_regex_extensions.dart:escapeForRegex()now usesm.group(0) ?? ''so the result never contains\null.
Maintenance
Documentation
- Lint-resolution details from
bugs/history/reflected in dartdoc, unit tests, and CHANGELOG entries (1.0.7, 1.0.8). - Lint rule rationale in
analysis_options_custom.yaml:avoid_barrel_files,avoid_non_ascii_symbols,avoid_static_state,avoid_unmarked_public_class;avoid_default_tostringsatisfied bySwipe.toString(). Six more rules resolved:avoid_collapsible_if,avoid_complex_conditions,avoid_redundant_else,avoid_medium_length_files,avoid_long_parameter_list,avoid_similar_names.
Tests
- Swipe.toString(), MapNullableExtensions (isMapNullOrEmpty, isNotMapNullOrEmpty), GestureUtils (getSwipeSpeed, swipeMagnitudeThresholds), obscureText, hasInvalidUnicode/removeInvalidUnicode (invalid code point 56327). Additional tests for new collections, graph, stats, validation, string, and async modules.
1.0.8 - 2026-02-24 #
In this release we introduce typed result classes for common operations, split JSON utilities into focused modules, and bring the code in line with lints (named parameters, narrower exceptions, @useResult). We aimed for clearer structure and safer APIs.
Added #
Occurrence<T>class (lib/iterable/occurrence.dart): typed result formostOccurrences()andleastOccurrences()methods, replacing record return typesBetweenResultclass (lib/string/between_result.dart): typed result forbetweenResult(),betweenResultLast(), and bracket-extraction methods, replacing record return typesGestureUtils(lib/gesture/gesture_utils.dart): extracted swipe speed/magnitude classification into standalone utility class with public thresholdsJsonEpochScaleenum (lib/json/json_epoch_scale.dart): epoch timestamp scale (seconds, milliseconds, microseconds) extracted fromjson_utils.dartJsonIterablesUtils(lib/json/json_iterables_utils.dart): generic JSON encoding for iterablesJsonTypeUtils(lib/json/json_type_utils.dart): 13 type-safe JSON conversion methods (lists, strings, ints, doubles, booleans, dates, epochs) extracted fromjson_utils.dart@useResultannotations: Added to 40+ public methods across string, datetime, gesture, json, list, map, num, and other extensions to prevent silent discard of return valuesKeyExtractor<T, E>typedef: fortoUniqueBy/toUniqueByInPlaceparameters (prefer_typedefs_for_callbacks)Swipe.toString(): Added string representation for debugging
Changed #
Swipeconstructor: Changed from positional to required named parameters (prefer_all_named_parameters)- Boolean parameter renames (
prefer_boolean_prefixes):testDecode→shouldTestDecode,allowEmpty→shouldAllowEmpty,cleanInput→shouldCleanInput,inclusive→isInclusive,startOfDay→isStartOfDay,roundUp→shouldRoundUp - Exception narrowing (
avoid_catch_all): Replaced barecatch (e)with specific exception types (on FormatException) dynamic→Object?: Replaceddynamicreturn types in JSON decode methods (avoid_dynamic_type)- Added
T extends Objectconstraint toGeneralIterableExtensionsgeneric parameter
Maintenance
Refactoring
json_utils.dartsplit: Extracted type conversions, epoch scale, and iterable encoding into 3 focused modules for modularity- Abstract final classes: Converted static-only utility classes to
abstract finalto prevent instantiation and inheritance - Lint compliance: Extracted
_writeFormattedValuehelper fromformatMapusing Dart 3 switch pattern matching - Lint compliance: Eliminated logic duplication —
inRangenow delegates toisBetween; replaced inline leap-year math withDateTimeUtils.isLeapYear()reuse - Lint compliance: Extracted hardcoded
Durationconstants (_oneDay,_oneMicrosecond) peravoid_hardcoded_durations - Refactoring: Extracted helper methods in
date_time_utils.dart(_pluralLabel,_joinWithAnd,_buildDurationParts) and replaced switch statements with constant set lookups
Lint
- Lint compliance: Resolved
prefer_all_named_parametersacrossisNthDayOfMonthInRange,getGreatGrandchild,getGreatGrandchildString,mapToggleValue,mapAddValue,mapRemoveValue,mapContainsValue - Lint compliance: Resolved
prefer_class_over_record_returnacross 5 extension files by replacing record types with named classes - Lint compliance: Resolved
prefer_parentheses_with_if_nullinstring_between_extensions.dart - Lint compliance: Resolved
prefer_typedefs_for_callbacksandprefer_extracting_function_callbacksin unique list and map extensions - Lint compliance: Used
List.generatefor pre-allocated day lists (require_list_preallocate) - Lint compliance: Avoided parameter mutation — use local
resolvedNowinstead of reassigningnow - Updated
analysis_options.yamlandanalysis_options_custom.yamllint configurations
Tests
- Added comprehensive test suite for
JsonTypeUtils(60+ test cases) - Updated tests for renamed boolean parameters across json, datetime, gesture, and enum tests
- Lint violations reduced from ~10,000 to ~30
1.0.7 - 2026-02-22 #
We split the large string and date-time extension files into smaller modules (everything stays backward compatible), fixed a bunch of lints, and switched to proper test matchers. The codebase is easier to work in and the linter is quieter.
Fixed #
- Performance: cached
toLowerCase()call intoBoolNullable, extracted inline RegExp to top-level finals inlowerCaseLettersOnlyandremoveSingleCharacterWords
Maintenance
Lint
- Lint compliance: Resolved 10 lint rule categories across 4 files:
avoid_nested_conditional_expressions(5): refactored nested ternaries to if-else in wrap methodsavoid_redundant_else(2): removed redundant else ingetFirstDiffChar,toBoolNullableprefer_switch_expression(2): convertedisVowel,grammarArticleto switch expressionsavoid_string_concatenation_loop(1): replaced string concat with StringBuffer insplitCapitalizedUnicodeavoid_duplicate_string_literals(1): reused_alphaOnlyRegexinlettersOnlyprefer_correct_identifier_length(1): renamedrtodeduplicateRegexinreplaceLineBreaksmissing_use_result_annotation(1): added@useResulttomakeNonBreakingno_magic_string(1): extracted grammar article prefixes to named constantsavoid_long_length_files(2): split oversized files (see Refactoring below)avoid_very_long_length_files(1): splitstring_extensions.dart(1114 lines)
- Lint compliance (prior): Resolved 59 high-priority warnings across 9 saropa_lints rules:
avoid_type_casts(7): replacedascasts withischecks in map/json utilsverify_documented_parameters_exist(31): fixed stale dartdoc referencesavoid_string_substring(9): replacedsubstring()withsubstringSafe()prefer_iterable_of(5): replaced.from()with.of()for type safetyavoid_duplicate_cascades(3): refactored UUID StringBuffer toList.join()avoid_nullable_interpolation(1): added??fallback inescapeForRegexavoid_unsafe_cast(1): used type promotion inmake_list_extensionsavoid_wildcard_cases_with_sealed_classes(1): narrowednumtointavoid_god_class(1): suppressed (constants namespace)
Refactoring
string_extensions.dart(1114 → 4 files): Split intostring_extensions.dart(275),string_analysis_extensions.dart(195),string_manipulation_extensions.dart(286),string_text_extensions.dart(296). Re-exports maintain backward compatibility.date_time_extensions.dart(818 → 4 files): Split intodate_time_extensions.dart(185),date_time_arithmetic_extensions.dart(175),date_time_comparison_extensions.dart(164),date_time_calendar_extensions.dart(174). Re-exports maintain backward compatibility.DateConstants: Moved 16 top-level constants intoDateConstantsclass asstatic constmembers for proper namespacing and consistency withMonthUtils,WeekdayUtils, andSerialDateUtilspatterns. Added private constructor to prevent instantiation.
Build/tooling
- Publish script (
publish_pub_dev.ps1): Hardened with smarter pre-checks — auto-fixes pubspec version when CHANGELOG is ahead, aborts early if version tag exists on remote, verifiesghauth status and publish workflow. Removed dead code, fixed docstrings and step numbering. Bumped to v2.2. - Bug reports for 80+ saropa_lints rules with reproduction steps and suggestions in
bugs/
Tests
- Replaced 312 raw literal matchers with proper test matchers across 19 test files (
avoid_misused_test_matchers):expect(x, true)→isTrue,expect(x, false)→isFalse,expect(x, null)→isNull,expect(x.length, N)→hasLength(N)
1.0.6 - 2026-02-19 #
We ran a full bug audit and fixed 32 issues—date/time and string logic, emoji handling, and JSON/HTML edge cases. Behavior should be more reliable everywhere.
Fixed (32 bugs resolved — full audit) #
Critical / Logic Errors
getUtcTimeFromLocal: was adding offset instead of subtracting; usedfloor()instead oftruncate()for negative fractional offsets (e.g. UTC-5:30). Return type narrowed fromDateTime?toDateTime(never null).isDateAfterToday: was an instance method that ignoredthisentirely, only checking thedateToCheckparameter. Removed the parameter — now correctly checks the receiver against today. Added injectable{DateTime? now}for testability.randomElement: was usingDateTime.now().microsecondsSinceEpoch % length(deterministic, biased). Now uses a module-levelRandominstance withnextInt().isBetween: inclusive mode was using==instead ofisAtSameMomentAsfor boundary equality — boundary values were excluded.removeStart: case-insensitive path was callingnullIfEmpty()on the trimmed match, returningnullinstead of the original string on non-match.last(): was using rune-based indexing, splitting multi-codepoint emoji. Now usescharacterspackage grapheme clusters. Also optimized: replacestoList()+sublist()withchars.skip()to avoid full list allocation.toDateInYear: was crashing withArgumentErrorfor Feb 29 → non-leap year. Now returnsnull.cleanJsonResponse: was unescaping\"before detecting outer quotes, corrupting strings like"hello \"world\"". Now detects outer quotes first.
Medium
betweenResult:endOptionalparameter was declared but never consulted — end-not-found always returnednull. Now correctly returns the tail whenendOptional: trueis passed. Default changed tofalseto preserve backward compatibility.isSameDateOrAfter/isSameDateOrBefore: replaced fragile cascaded year/month/day if-chains with cleantoDateOnly()+!isBefore/!isAfter.isJson('[]'): empty array was returningtruewithoutallowEmpty: true, inconsistent with empty object{}behavior. Now requiresallowEmpty: truefor both.isJsoncolon check: was checkingvalue.contains(':')(untrimmed) instead oftrimmed.contains(':').formatDouble: no guard for negativedecimalPlaces—toStringAsFixedwould throwRangeError. Now clamps to 0–20.hasDecimals/formatDouble: did not guard againstNaN/Infinity—NaN % 1returnsNaN(not0). Now returnsfalse/'NaN'/'∞'respectively.unescape(HTML): was mapped to regular space (U+0020) instead of non-breaking space (U+00A0). Fixed.unescape(HTML): numeric entity handler allowed surrogate codepoints (U+D800–U+DFFF) which crashjsonEncode. Now rejected with named constants_surrogateMin/_surrogateMax.addHyphens: accepted any 32-char string without validating hex content. Now validates with_hexOnly32Regex.exclude/containsAny: O(n×m) — converted toSetfor O(n) lookup.toFlattenedList: returnednullfor empty outer but[]for all-empty inners. Now returnsnullconsistently for empty results.
Low / Documentation
isYearCurrent: hardcodedDateTime.now()made it untestable. Converted from getter to method with{DateTime? now}injectable parameter.isDateAfterToday/isTodayetc.: same injectablenowpattern applied for testability.weekOfYear: added warning in docs that value can be 0 or 53 at year boundaries; recommendweekNumber()for ISO 8601 compliance.isMidnight: now checks all time components including milliseconds and microseconds.leastOccurrences: corrected copy-paste doc comment that said "highest" instead of "lowest".formatPrecision: hardcodedtoStringAsFixed(2)whole-number check now uses the actualprecisionparameter.betweenResult: improved doc to explain intentionallastIndexOf("outermost match") design.between: documented special case where emptyendreturns the tail fromstart.takeSafe(0): documented thatcount == 0returns the original list (unliketake(0)).weekOfYear/weekNumber(): documented ISO 8601 edge cases at year boundaries.num.length(): documented scientific notation behavior for values ≥ 1e21.pluralize: removedlength == 1guard that incorrectly skipped single-character strings.forceBetween: corrected misleading dartdoc ("NOT greater than" → correctly describes clamping).truncateWithEllipsisPreserveWords: fixed grapheme-unsafe fallback that could split multi-codepoint emoji; now usescharacters.take()for the search window.toMapStringDynamic: documented silent key collision behavior whenensureUniqueKey: false.timeToEmoji: boundary was>instead of>=— 7:00am showed moon emoji instead of sun.
Maintenance
Refactoring
- Extracted magic numbers into named constants across codebase (date/time, numeric, string, HTML, UUID); 50+ constants in
date_constants.dart,date_time_range_utils.dart,date_time_utils.dart,time_emoji_utils.dart,double_extensions.dart,hex_utils.dart,html_utils.dart,int_extensions.dart,int_string_extensions.dart,int_utils.dart,string_search_extensions.dart,string_utils.dart,uuid_utils.dart; resolvedno_magic_numberlint violations.
Lint
DateTimeUtils.tomorrow(): Removed nullable type fromminuteandsecondparameters to fixavoid_nullable_parameters_with_default_valueslint warnings.
Tests
- 3,022 tests passing (added ~40 new tests covering all fixed bugs)
- Fixed 8 pre-existing tests with incorrect expectations or wrong test names
- Removed duplicate test cases in
date_time_range_utils_test.dart
1.0.5 - 2026-01-08 #
We rewrote the README with before/after examples and real-world use cases so it’s easier to see what the library does and whether it fits your project.
Maintenance
Documentation
- Rewrote README with compelling production-proven messaging
- Added before/after code comparison table
- Added real-world use cases section
- Improved About section with library origin story
1.0.4 - 2026-01-08 #
We fixed a flaky date/time test that sometimes failed in CI. Your test runs should be more reliable now.
Maintenance
Tests
- Fix flaky DateTime test race condition in CI
1.0.3 - 2026-01-07 #
We updated the GitHub Actions publish workflow to use OIDC authentication. Publishing to pub.dev works with the current GitHub setup again.
Maintenance
Build/tooling
- Fix GitHub Actions publish workflow for OIDC authentication
1.0.2 - 2026-01-07 #
We added a banner to the README so the project is easier to spot at a glance.
Maintenance
Documentation
- Added a banner to README.md
1.0.0 - 2026-01-07 #
First stable 1.0: we switched to the MIT license for broader use, turned on the full saropa_lints tier for quality, and added README badges so you can see pub points, method count, and coverage at a glance.
Changed #
- Migrated from GPL v3 to MIT license for broader adoption
Maintenance
Lint
- Upgraded saropa_lints from
recommendedtoinsanitytier (all 500+ rules enabled)
Documentation
- Pub points badge (dynamic from pub.dev)
- Methods count badge (480+ methods)
- Coverage badge (100%)
- Organized badge assets into
assets/badges/folder
0.5.12 - 2026-01-05 #
We switched to the saropa_lints package and custom_lint, and trimmed the analysis config from 255 lines to 69. You get the same level of checking with less to maintain.
Maintenance
Build/tooling
- Replaced manually flattened lint rules with
saropa_lints: ^1.1.12 - Added
custom_lint: ^0.8.0for custom lint rule support - Configured
recommendedtier (~150 rules) - Simplified
analysis_options.yamlfrom 255 lines to 69 lines - Removed manually flattened flutter_lints/recommended/core rules
0.5.11 #
We added utilities for Base64 compression, UUID validation and formatting, HTML unescape and plain text, and double formatting (percentages, precision, clamping). All of it is covered by 103 new tests.
Added #
Base64Utils- Text compression and decompression (compressText,decompressText)UuidUtils- UUID validation and manipulation (isUUID,addHyphens,removeHyphens)HtmlUtils- HTML text processing (unescape,removeHtmlTags,toPlainText)DoubleExtensions- Double formatting (hasDecimals,toPercentage,formatDouble,forceBetween,toPrecision,formatPrecision)
Maintenance
Tests
- 103 test cases covering all new utilities
0.5.10 - 2025-12-11 #
We extended the publish script with version and branch parameters, dry-run validation, and checks for working tree and remote sync. Releases are safer and easier to script from CI.
Maintenance
Build/tooling
-Versionparameter for CI/CD automation in publish script-Branchparameter to specify target branch- Pre-publish validation step (
flutter pub publish --dry-run) flutter analyzestep before publishing- Working tree status check with user confirmation
- Remote sync check to prevent publishing when behind remote
- Early CHANGELOG version validation
- Step numbering in publish script (was skipping from 4 to 6)
ErrorActionPreferenceissue with try/catch for GitHub release check- Dynamic package name and repo URL extraction from pubspec.yaml and git remote
- Excluded example folder from parent analysis
Older versions (0.5.9 and earlier): see CHANGELOG_HISTORY.md.
Made by Saropa. All rights reserved.