Skip to content

Conversation

@christerswahn
Copy link
Collaborator

@christerswahn christerswahn commented Sep 14, 2025

This reverts two previous commits, for the cli_tools and config packages respectively, that forced explicit generic type specification in all usages of the packages' classes. This made for a worse developer experience without adding any value.

By reverting these commits we will be using Dart's default rules.

(Note: Since serverpod_lints currently enables stricter rules than Dart's default, they are explicitly disable here. When serverpod_lints removes these as is planned, this can be removed here as well.)

Summary by CodeRabbit

  • Refactor

    • Simplified and relaxed generics across CLI and config APIs for easier usage (fewer explicit type arguments).
    • Simplified callback signatures and constructor usage; improved API ergonomics.
    • Loosened Comparable constraints and streamlined mock I/O method signatures.
  • Documentation

    • Updated examples to reflect unbounded generics and current API.
  • Tests

    • Migrated tests to non-generic runner/command usage; adjusted assertions and minor async handling.
  • Chores

    • Updated analyzer settings to relax strict raw types and ignore certain inference-related warnings.

@coderabbitai
Copy link

coderabbitai bot commented Sep 14, 2025

📝 Walkthrough

Walkthrough

This PR relaxes generics across config and CLI tools, simplifying type bounds and removing explicit type arguments. It updates BetterCommandRunner’s typedef and bounds, adjusts examples and tests to non-generic usages, tweaks several test utility method signatures, and adds analyzer settings to allow raw types and ignore select inference errors.

Changes

Cohort / File(s) Summary of changes
Analyzer settings
packages/cli_tools/analysis_options.yaml, packages/config/analysis_options.yaml
Added analyzer.language.strict-raw-types: false and ignored select inference errors; existing lints unchanged.
BetterCommandRunner API
packages/cli_tools/lib/src/better_command_runner/better_command_runner.dart
Simplified OnBeforeRunCommand typedef to non-generic; updated BetterCommandRunner bound from OptionDefinition to OptionDefinition; adjusted field/constructor types accordingly.
Documentation generator typing
packages/cli_tools/lib/src/documentation_generator/documentation_generator.dart
Narrowed field type to non-parameterized BetterCommandRunner; removed unused OptionDefinition import.
Examples (CLI tools)
packages/cli_tools/example/main.dart, packages/cli_tools/example/simple_command_example.dart
Removed explicit type arguments when constructing BetterCommandRunner.
Config public API generic relaxations
packages/config/lib/src/config/options.dart, .../option_types.dart, .../option_resolution.dart, .../configuration.dart
Dropped extends Object on V across OptionDefinition, ConfigOptionBase, OptionResolution; changed Comparable to Comparable; updated public methods and doc samples to unbounded V.
Config examples and README
packages/config/example/*.dart, packages/config/README.md
Updated enum declarations to unbounded V in examples and README snippets.
CLI tools tests: runner/command typing
packages/cli_tools/test/better_command_runner/*
Removed OptionDefinition imports; switched Command/BetterCommand<OptionDefinition, void> to non-generic Command/BetterCommand; instantiated BetterCommandRunner without type args; minor signature updates (e.g., runWithConfig types).
CLI tools tests: docs generator
packages/cli_tools/test/documentation_generator/generate_markdown_test.dart
Dropped generics from command classes; updated runWithConfig signatures to unbounded V; constructed BetterCommandRunner without type args.
CLI tools tests: misc
packages/cli_tools/test/pub_api_client_test.dart
Replaced Future.delayed with Future.delayed.
Test utilities (streams/stdout)
packages/cli_tools/test/test_utils/mock_stdin.dart, packages/cli_tools/test/test_utils/mock_stdout.dart
Changed Future returns to Future; adjusted writeAll to take Iterable.
Test utilities (matchers)
packages/cli_tools/test/test_utils/prompts/option_matcher.dart
Replaced Map<dynamic, dynamic> with raw Map in matcher method signatures.
Config tests: lints and types
packages/config/test/config/args_compatibility/command_runner_test.dart, .../test_utils.dart, .../configuration_test.dart
Removed strict_raw_type ignores; updated _TestOption to unbounded V; switched error assertions to toString() contains checks.

Sequence Diagram(s)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • nielsenko
  • vlidholt
  • Isakdl

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "revert: Do not force strict generic types" clearly and concisely communicates the PR’s primary action—reverting a change that enforced strict generic type usage—so a reviewer scanning history can understand the main intent without extra detail. It is specific, short, and directly related to the changes described in the PR objectives and summary.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-raw

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@christerswahn christerswahn marked this pull request as ready for review September 14, 2025 10:38
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/cli_tools/lib/src/documentation_generator/documentation_generator.dart (1)

26-34: Probable missing import for .indexed extension.

entries.indexed typically comes from package:collection/collection.dart. Without it, this may fail to analyze/build.

Apply:

-import '../../better_command_runner.dart' show BetterCommandRunner;
+import '../../better_command_runner.dart' show BetterCommandRunner;
+import 'package:collection/collection.dart';
packages/config/test/config/configuration_test.dart (1)

1-1: Replace OptionDefinitionError.message assertions with toString() checks.

Two tests still assert on .message — update them to assert on e.toString() (e.g. .having((e) => e.toString(), 'message', contains('...'))).

  • packages/config/test/config/configuration_test.dart:1270–1272
  • packages/config/test/config/configuration_test.dart:1440–1442
🧹 Nitpick comments (9)
packages/cli_tools/analysis_options.yaml (1)

3-9: Temporary lint relaxations: add an inline TODO for later reversal

These settings match the PR intent. Add a TODO so we don’t forget to revert when serverpod_lints drops the stricter rules.

Apply this small annotation:

 analyzer:
   language:
     strict-raw-types: false
+    # TODO(serverpod/cli_tools#64): Temporary relaxations to support reverting explicit generics.
+    # Remove once serverpod_lints aligns with Dart defaults.
   errors:
     inference_failure_on_instance_creation: ignore
     inference_failure_on_function_invocation: ignore
packages/config/analysis_options.yaml (1)

3-10: Analyzer relaxations: annotate as temporary and consider future tightening

Settings align with the revert. Add a TODO so we can restore stricter checks later (or scope ignores to tests when feasible).

Suggested annotation:

 analyzer:
   language:
     strict-raw-types: false
+    # TODO(serverpod/config#64): Temporary relaxations while reverting explicit generics.
+    # Revisit once serverpod_lints matches Dart defaults.
   errors:
     inference_failure_on_instance_creation: ignore
     inference_failure_on_function_invocation: ignore
     inference_failure_on_untyped_parameter: ignore
packages/config/lib/src/config/options.dart (1)

450-454: Remove redundant cast after type check.

value is already promoted to V by if (value is V), so as V is unnecessary.

-      return OptionResolution(
-        value: value as V,
-        source: ValueSourceType.config,
-      );
+      return OptionResolution(
+        value: value,
+        source: ValueSourceType.config,
+      );
packages/config/lib/src/config/option_types.dart (1)

164-164: Loosened bound uses raw Comparable; consider a slightly safer bound.

Using raw Comparable drops static checks on compareTo. If it doesn’t fight the revert’s goals, consider Comparable<Object?> to keep broad compatibility (e.g., DateTime, int) while retaining a generic argument.

-class ComparableValueOption<V extends Comparable> extends ConfigOptionBase<V> {
+class ComparableValueOption<V extends Comparable<Object?>> extends ConfigOptionBase<V> {
packages/cli_tools/example/simple_command_example.dart (1)

32-47: Optional: align example’s generic bound with library (drop extends Object).

Keeps the example consistent with the unbounded generic in OptionDefinition.

-enum ShowOption<V extends Object> implements OptionDefinition<V> {
+enum ShowOption<V> implements OptionDefinition<V> {
packages/cli_tools/test/better_command_runner/exit_exceptions_test.dart (1)

31-33: Quiet test logs for cleanliness (nit).

Capture usage/exception output to avoid noisy stderr in CI.

-final runner = BetterCommandRunner('test', 'this is a test cli')
-  ..addCommand(MockCommand());
+final runner = BetterCommandRunner(
+  'test',
+  'this is a test cli',
+  messageOutput: const MessageOutput(),
+)
+  ..addCommand(MockCommand());
packages/config/lib/src/config/configuration.dart (2)

204-220: Type inference on findValueOf can default to dynamic

Call sites relying on booleans should prefer explicit type args for clarity and static safety, e.g. config.findValueOf<bool>(argName: 'verbose').


260-264: Add a debug-time type assertion to surface mismatches earlier

The unchecked cast may hide misconfigured option types at runtime. Add an assert to aid debugging without changing release behavior.

 V? optionalValue<V>(final OptionDefinition<V> option) {
   final resolution = _getOptionResolution(option);
-
-  return resolution.value as V?;
+  assert(
+    resolution.value == null || resolution.value is V,
+    'Resolved value type does not match the option generic type V',
+  );
+  return resolution.value as V?;
 }
packages/cli_tools/test/better_command_runner/better_command_test.dart (1)

100-100: Avoid fixed 100ms sleeps; yield the event loop instead

Yielding once is enough and removes flakiness.

-      await Future.delayed(const Duration(milliseconds: 100));
+      await Future<void>.delayed(Duration.zero);

Apply same replacement in packages/cli_tools/test/better_command_runner/better_command_test.dart at lines 100, 110, 120, 130, 141

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f4bac8 and 193cbb1.

📒 Files selected for processing (28)
  • packages/cli_tools/analysis_options.yaml (1 hunks)
  • packages/cli_tools/example/main.dart (1 hunks)
  • packages/cli_tools/example/simple_command_example.dart (1 hunks)
  • packages/cli_tools/lib/src/better_command_runner/better_command_runner.dart (3 hunks)
  • packages/cli_tools/lib/src/documentation_generator/documentation_generator.dart (1 hunks)
  • packages/cli_tools/test/better_command_runner/analytics_test.dart (7 hunks)
  • packages/cli_tools/test/better_command_runner/better_command_test.dart (11 hunks)
  • packages/cli_tools/test/better_command_runner/command_test.dart (1 hunks)
  • packages/cli_tools/test/better_command_runner/default_flags_test.dart (1 hunks)
  • packages/cli_tools/test/better_command_runner/exit_exceptions_test.dart (2 hunks)
  • packages/cli_tools/test/better_command_runner/logging_test.dart (2 hunks)
  • packages/cli_tools/test/better_command_runner/parse_log_level_test.dart (3 hunks)
  • packages/cli_tools/test/documentation_generator/generate_markdown_test.dart (7 hunks)
  • packages/cli_tools/test/pub_api_client_test.dart (1 hunks)
  • packages/cli_tools/test/test_utils/mock_stdin.dart (1 hunks)
  • packages/cli_tools/test/test_utils/mock_stdout.dart (2 hunks)
  • packages/cli_tools/test/test_utils/prompts/option_matcher.dart (4 hunks)
  • packages/config/README.md (1 hunks)
  • packages/config/analysis_options.yaml (1 hunks)
  • packages/config/example/config_file_example.dart (1 hunks)
  • packages/config/example/main.dart (1 hunks)
  • packages/config/lib/src/config/configuration.dart (5 hunks)
  • packages/config/lib/src/config/option_resolution.dart (1 hunks)
  • packages/config/lib/src/config/option_types.dart (1 hunks)
  • packages/config/lib/src/config/options.dart (5 hunks)
  • packages/config/test/config/args_compatibility/command_runner_test.dart (1 hunks)
  • packages/config/test/config/args_compatibility/test_utils.dart (1 hunks)
  • packages/config/test/config/configuration_test.dart (4 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-06-12T14:55:38.006Z
Learnt from: christerswahn
PR: serverpod/cli_tools#47
File: lib/src/config/options.dart:552-567
Timestamp: 2025-06-12T14:55:38.006Z
Learning: The codebase relies on a recent version of `package:args` where `ArgParser.addFlag` accepts the `hideNegatedUsage` parameter.

Applied to files:

  • packages/config/test/config/args_compatibility/test_utils.dart
📚 Learning: 2025-08-07T07:55:17.269Z
Learnt from: christerswahn
PR: serverpod/cli_tools#57
File: packages/config/test/better_command_runner/default_flags_test.dart:1-1
Timestamp: 2025-08-07T07:55:17.269Z
Learning: In the `config` package, `better_command_runner.dart` is intentionally kept as a separate import (`package:config/better_command_runner.dart`) rather than being re-exported through the main `packages/config/lib/config.dart` barrel file. This separation is by design according to the package maintainer christerswahn.

Applied to files:

  • packages/config/test/config/args_compatibility/command_runner_test.dart
  • packages/cli_tools/example/main.dart
  • packages/cli_tools/lib/src/documentation_generator/documentation_generator.dart
  • packages/cli_tools/example/simple_command_example.dart
  • packages/cli_tools/test/better_command_runner/command_test.dart
  • packages/cli_tools/test/better_command_runner/default_flags_test.dart
  • packages/cli_tools/test/better_command_runner/parse_log_level_test.dart
  • packages/cli_tools/test/better_command_runner/better_command_test.dart
📚 Learning: 2025-05-02T12:40:13.755Z
Learnt from: christerswahn
PR: serverpod/cli_tools#41
File: lib/src/better_command_runner/better_command.dart:55-61
Timestamp: 2025-05-02T12:40:13.755Z
Learning: In BetterCommand, null is a valid initializer for messageOutput, and when initialized with null, the getter should return null rather than inherit from the runner's instance. This allows users to explicitly opt out of message output.

Applied to files:

  • packages/cli_tools/test/better_command_runner/parse_log_level_test.dart
  • packages/cli_tools/test/better_command_runner/better_command_test.dart
🔇 Additional comments (47)
packages/config/test/config/args_compatibility/test_utils.dart (1)

5-5: Lint suppression cleanup looks good

Removing the strict_raw_type ignore aligns with the new analyzer settings. No further action.

packages/config/test/config/args_compatibility/command_runner_test.dart (1)

1-1: Lint directive update is consistent

Dropping strict_raw_type from the ignore list matches package-level analyzer config. Looks good.

packages/config/README.md (1)

93-93: Doc sample updated to unbounded generic — LGTM

The LogOption<V> bound now matches the library API after the revert. No further changes needed.

packages/cli_tools/test/test_utils/mock_stdin.dart (1)

162-162: Signature now matches Stream.pipe — good change

Returning Future (non-generic) matches the core Stream API and avoids unnecessary dynamic.

packages/config/lib/src/config/option_resolution.dart (1)

3-3: Removed extends Object bound — verify nullable generic scenarios

This broadens V to allow nullable types. Ensure existing tests cover OptionResolution<String?> (and similar) to confirm value handling and hasValue/isDefault logic behave as expected.

packages/cli_tools/test/test_utils/prompts/option_matcher.dart (1)

15-15: Override signatures now match Matcher exactly

Using raw Map for matchState mirrors package:test’s Matcher API and avoids generic-mismatch noise. Approved.

Also applies to: 42-42, 71-71, 88-88

packages/cli_tools/test/test_utils/mock_stdout.dart (1)

28-35: Signatures aligned with Stdout/IOSink; looks good.

Return types and writeAll parameter now match the platform interfaces.

Also applies to: 38-43, 70-72

packages/cli_tools/example/main.dart (1)

5-12: Generic removal on BetterCommandRunner is consistent with the revert.

No behavior change; improves ergonomics.

packages/cli_tools/test/pub_api_client_test.dart (1)

17-19: Minor cleanup: prefer Future.delayed over Future<void>.delayed.

Equivalent behavior; clearer.

packages/config/example/config_file_example.dart (1)

43-64: Enum bound relaxed to unbounded V; matches library changes.

Keeps examples in sync with the config API.

packages/cli_tools/test/better_command_runner/command_test.dart (1)

36-43: Runner variable now non-generic; consistent with API change.

Tests remain clear and focused.

packages/config/example/main.dart (1)

44-73: Enum bound relaxation aligns with the library update.

Example stays consistent with the new typing model.

packages/cli_tools/test/better_command_runner/default_flags_test.dart (1)

6-11: LGTM: raw BetterCommandRunner usage matches the revert.

Looks correct; tests remain behaviorally identical.

packages/cli_tools/lib/src/documentation_generator/documentation_generator.dart (1)

4-4: LGTM: field narrowed to non-generic BetterCommandRunner.

Consistent with the API revert; no behavior change here.

packages/cli_tools/example/simple_command_example.dart (1)

17-20: LGTM: constructor now relies on type inference.

packages/cli_tools/test/better_command_runner/logging_test.dart (2)

5-27: LGTM: non-generic Command in tests.


33-41: LGTM: non-generic BetterCommandRunner with captured output.

packages/cli_tools/test/better_command_runner/exit_exceptions_test.dart (1)

5-27: LGTM: non-generic Command in tests.

packages/cli_tools/test/better_command_runner/analytics_test.dart (1)

9-28: LGTM: migrated commands and runner to non-generic forms.

Consistent across scenarios; analytics assertions unchanged.

Also applies to: 30-53, 56-56, 58-63, 78-83, 97-103, 210-216, 275-281

packages/cli_tools/test/better_command_runner/parse_log_level_test.dart (3)

5-16: LGTM: non-generic Command in tests.


27-38: LGTM: runner with setLogLevel callback (non-generic).


95-105: LGTM: runner with registered command (non-generic).

packages/config/test/config/configuration_test.dart (4)

15-20: LGTM: assert on OptionDefinitionError via toString().

More resilient to API changes.


35-40: LGTM: same for mandatory-with-default case.


58-63: LGTM: same for fromDefault function case.


1628-1650: LGTM: drop extends Object bound on test enum generic.

Matches the relaxed generic in the library.

packages/config/lib/src/config/configuration.dart (3)

20-31: Docs update to unbounded generics looks good

Example now matches the reverted, inference-friendly API.


241-254: value() null-safety flow is correct

Non-null preconditions + promotion make the return safe.


273-292: Private resolver signature consistency

Dropping the bound on V here keeps it aligned with the public API changes.

packages/cli_tools/lib/src/better_command_runner/better_command_runner.dart (5)

57-58: Relaxed bound to O extends OptionDefinition

Matches the revert intent and improves ergonomics.


65-65: Field type updated to non-generic callback — OK

Consistent with typedef change.


146-147: Constructor param type updated — OK

Public surface remains source-compatible for callers passing a function.


291-291: Pre-run hook invocation order is sensible

Hook runs after analytics scheduling and before the command; awaited to propagate errors — good.


9-9: Public API change: OnBeforeRunCommand dropped generic — add migration note

Good simplification. Add a short migration/changelog entry: callers using typed callbacks (OnBeforeRunCommand) will no longer compile.

Location: packages/cli_tools/lib/src/better_command_runner/better_command_runner.dart

Re-scan repo for typed usages:
rg -nP --type=dart -C2 '\bOnBeforeRunCommand\s*<'

packages/cli_tools/test/better_command_runner/better_command_test.dart (6)

19-30: Drop of generics on MockCommand matches API — OK


41-43: runWithConfig signature aligned with non-generic Configuration — OK


59-65: Non-generic BetterCommandRunner construction — OK


159-165: Explicit empty global options list with relaxed generics — OK


208-213: Runner without analytics correctly omits analytics flag in usage — OK


257-263: Runner with bespoke global options — OK

Covers the custom enum path alongside the default.

packages/cli_tools/test/documentation_generator/generate_markdown_test.dart (7)

8-22: Commands drop explicit void type — OK

Tests rely only on side effects; dynamic return is fine here.


24-38: RemoveSpiceCommand non-generic — OK


40-53: AddVegetableCommand non-generic — OK


55-69: SpiceCommand extends non-generic BetterCommand — OK


76-94: VegetableCommand updates mirror SpiceCommand — OK


101-104: Runner instantiation without generics in setup — OK

Confirms docs generator works against the relaxed API.


71-74: runWithConfig widened return type — OK

BetterCommand now declares FutureOr?; I checked overrides in examples and tests — they are compatible (examples return void for T=void; tests use FutureOr?). No further changes required.

Copy link
Contributor

@nielsenko nielsenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎸

.. left a few comments to consider, but feel free to close them directly.

@christerswahn christerswahn merged commit 4c5cc3a into main Sep 15, 2025
11 checks passed
@christerswahn christerswahn deleted the fix-raw branch September 15, 2025 07:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants