diff --git a/README.md b/README.md index d606eea..39a135e 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ npx skills add kevmoo/dash_skills --skill | **[dart-doc-validation](skills/dart-doc-validation/SKILL.md)** | Best practices for validating Dart documentation comments. Covers using `dart doc` to catch unresolved references and macros. | Documentation comment validation, Unresolved reference checking, Dart doc macro verification | | **[dart-long-lines](skills/dart-long-lines/SKILL.md)** | Guidelines for handling long lines in Dart code to adhere to the 80-column rule. The `lines_longer_than_80_chars` lint. | 80-column rule compliance, Line length refactoring, Linter rule enforcement | | **[dart-matcher-best-practices](skills/dart-matcher-best-practices/SKILL.md)** | Best practices for using `expect` and `package:matcher`. Focuses on readable assertions, proper matcher selection, and avoiding common pitfalls. | Package matcher assertions, Readable test expectations, Matcher selection & refactoring | -| **[dart-modern-features](skills/dart-modern-features/SKILL.md)** | Guidelines for using modern Dart features (v3.0 - v3.10) such as Records, Pattern Matching, Switch Expressions, Extension Types, Class Modifiers, Wildcards, Null-Aware Elements, and Dot Shorthands. | Records & Pattern Matching, Switch Expressions & Extension Types, Class Modifiers & Null-aware elements | +| **[dart-modern-features](skills/dart-modern-features/SKILL.md)** | Guidelines for using modern Dart features (v3.0 - v3.10) such as Records, Extension Types, Class Modifiers, Wildcards, Digit Separators, Null-Aware Elements, and Dot Shorthands. | Records & Extension Types, Class Modifiers & Wildcards, Null-aware elements & Dot shorthands | | **[dart-multiline-strings](skills/dart-multiline-strings/SKILL.md)** | Guidelines and best practices for refactoring consecutive prints, single-line string concatenations, and complex output blocks into triple-quoted multi-line string literals (''' or """) in Dart. | Triple-quoted multiline strings, Print & concatenation refactoring, Formatting large text blocks | | **[dart-package-maintenance](skills/dart-package-maintenance/SKILL.md)** | Guidelines for maintaining external Dart packages, covering versioning, publishing workflows, and pull request management. Use when updating Dart packages, preparing for a release, or managing collaborative changes in a repository. | Versioning & CHANGELOG sync, Publishing workflow guidelines, Pull request management | | **[dart-seal-type-hierarchies](skills/dart-seal-type-hierarchies/SKILL.md)** | Identify closed type hierarchies that are not declared `sealed`, and seal them so the compiler can enforce switch exhaustiveness. Covers the same-library requirement, the public-API breaking-change tradeoff, and the migration from `is` cascades to exhaustive switches. | Closed hierarchy detection, Exhaustiveness enforcement, Public API breaking-change analysis | diff --git a/skills/dart-modern-features/SKILL.md b/skills/dart-modern-features/SKILL.md index fe894dd..f425457 100644 --- a/skills/dart-modern-features/SKILL.md +++ b/skills/dart-modern-features/SKILL.md @@ -2,12 +2,12 @@ name: dart-modern-features description: |- Guidelines for using modern Dart features (v3.0 - v3.10) such as Records, - Pattern Matching, Switch Expressions, Extension Types, Class Modifiers, - Wildcards, Null-Aware Elements, and Dot Shorthands. + Extension Types, Class Modifiers, Wildcards, Digit Separators, Null-Aware + Elements, and Dot Shorthands. key_features: - - Records & Pattern Matching - - Switch Expressions & Extension Types - - Class Modifiers & Null-aware elements + - Records & Extension Types + - Class Modifiers & Wildcards + - Null-aware elements & Dot shorthands --- # Dart Modern Features @@ -17,9 +17,15 @@ key_features: Use this skill when: - Writing or reviewing Dart code targeting Dart 3.0 or later. -- Refactoring legacy Dart code to use modern, concise, and safe features. -- Looking for idiomatic ways to handle multiple return values, deep data - extraction, or exhaustive checking. +- Refactoring legacy Dart code to use modern, concise, and zero-cost features + such as records, extension types, class modifiers, null-aware collection + elements, dot shorthands, and digit separators. +- Looking for idiomatic ways to handle multiple return values or zero-cost + domain wrappers. + +For pattern matching, switch expressions, and list/map/split destructuring, use +**[dart-use-pattern-matching]**. For converting closed class hierarchies into +exhaustive `sealed` types, use **[dart-seal-type-hierarchies]**. ### When NOT to use (Abstention Guardrails) @@ -28,42 +34,14 @@ Do NOT apply modern features or refactor code when: - **SDK Constraint < 3.0.0**: The package's `pubspec.yaml` specifies an SDK constraint that supports Dart 2.x (e.g., `sdk: '>=2.19.0 <4.0.0'`). Refactoring to Dart 3 features will introduce syntax errors for Dart 2 users. -- **Single-Variable Type Promotion**: Checking a single variable or parameter - where standard `if (x is Foo)` is clearer, more concise, and avoids creating - unnecessary alias variables compared to `if (x case final Foo f)`. -- **Non-Algebraic Boolean Branching**: Branching on independent boolean flags, - side-effecting conditions, or early-exit guard clauses - (`if (!condition) return;`). Do not force these into switch expressions. -- **Deep Expression Nesting**: Complex multi-step operations where converting a - switch statement into a deeply nested switch expression obscures intent, harms - debugger step-through capability, or hurts stack trace readability. +- **Public API Records with > 3 Fields**: Records work well for 2–3 return + values or internal tuples, but complex public API payloads are clearer and + more extensible as dedicated classes or extension types. ## Discovery To find candidates for modernization: -### Switch Expressions - -Search for switch statements where every case assigns to the same variable or -returns: - -- **Regex**: `switch\s*\([^)]+\)\s*\{\s*case` - -### Pattern Matching Candidates - -Search for manual map/JSON property extraction, type checking, or list/segment -indexing: - -- **Regex**: `containsKey\(['"][^'"]+['"]\)` -- **Regex**: `json\[['"][^'"]+['"]\]\s+is\s+` -- **Regex**: `\.length\s*(>=|==|>|<)\s*\d+` (Combined with manual list index - access like `\[0\]`, `\[1\]`, `.first`, or `\[\w+\.length\s*-\s*\d+\]`). -- **Regex**: `\.skip\(1\)|\.sublist\(1\)` (Tail slicing replaceable by - `[head, ...final rest]`). -- **Regex**: `\.split\([^)]+\)` (Followed by `.length` or index checks; note - that matching `[final first, final second, ...final rest]` guarantees at least - one delimiter was present). - ### Null-Aware Elements Search for collection `if` statements checking for null: @@ -111,104 +89,35 @@ void main() { } ``` -### Patterns and Pattern Matching - -Use patterns to destructure complex data into local variables and match against -specific shapes or values. Use them in `switch`, `if-case`, or variable -declarations to unpack data directly. +### Class Modifiers -**Avoid:** Manually checking types, nulls, and keys for data extraction. +Use class modifiers (`final`, `base`, `interface`, `sealed`) to restrict how +classes can be subtyped outside their defining library: -```dart -void processJson(Map json) { - if (json.containsKey('name') && json['name'] is String && - json.containsKey('age') && json['age'] is int) { - String name = json['name']; - int age = json['age']; - print('$name is $age years old.'); - } -} -``` +- `interface class`: External libraries may `implement`, but cannot `extend`. +- `base class`: External libraries may `extend`, but cannot `implement` + (preserving private implementation invariants). +- `final class`: External libraries can neither `extend` nor `implement`. +- `sealed class`: Closed family of subtypes within the same library enabling + exhaustive switching (see **[dart-seal-type-hierarchies]**). -**Prefer:** Combining type-checking, validation, and assignment into a single -statement. +**Avoid:** Leaving internal implementation classes open to arbitrary external +subclassing or interface implementation when invariants must be enforced. ```dart -void processJson(Map json) { - if (json case {'name': String name, 'age': int age}) { - print('$name is $age years old.'); - } +class TokenStore { + void save(String token) {} } ``` -### Switch Expressions - -Use switch expressions to return a value directly, eliminating bulky `case` and -`break` statements. - -**Avoid:** Using switch statements where every branch simply returns or assigns -a value. +**Prefer:** Declaring explicit subtyping capabilities with class modifiers. ```dart -String describeStatus(int code) { - switch (code) { - case 200: - return 'Success'; - case 404: - return 'Not Found'; - default: - return 'Unknown'; - } +final class TokenStore { + void save(String token) {} } ``` -**Prefer:** Returning the evaluated expression directly using the `=>` syntax. - -```dart -String describeStatus(int code) => switch (code) { - 200 => 'Success', - 404 => 'Not Found', - _ => 'Unknown', -}; -``` - -### Class Modifiers - -Use class modifiers (`sealed`, `final`, `base`, `interface`) to restrict how -classes can be used outside their defines library. Prefer `sealed` for defining -closed families of subtypes to enable exhaustive checking. - -**Avoid:** Using open `abstract` classes when the set of subclasses is known and -fixed. - -```dart -abstract class Result {} - -class Success extends Result {} -class Failure extends Result {} - -String handle(Result r) { - if (r is Success) return 'OK'; - if (r is Failure) return 'Error'; - return 'Unknown'; -} -``` - -**Prefer:** Using `sealed` to guarantee to the compiler that all cases are -covered. - -```dart -sealed class Result {} - -class Success extends Result {} -class Failure extends Result {} - -String handle(Result r) => switch(r) { - Success() => 'OK', - Failure() => 'Error', -}; -``` - ### Extension Types Use extension types for a zero-cost wrapper around an existing type. Use them to @@ -312,132 +221,18 @@ LogLevel currentLevel = LogLevel.info; LogLevel currentLevel = .info; ``` -### Pragmatic Balance: When NOT to Over-Patternize - -Pattern matching and switch expressions should simplify code, not add syntactic -overhead. - -#### 1. Prefer `is` Type Promotion over `if-case` for Single Variables - -If you only need to check a type or promote a variable, use standard `is` checks -instead of `if-case` or `case` patterns that introduce shadow aliases. - -**Avoid:** - -```dart -// ❌ Anti-pattern: Introduces unnecessary alias variable `k` -for (final MapEntry(:key, :value) in map.entries) { - if (key case final String k when value != null) { - process(k, value); - } -} -``` - -**Prefer:** - -```dart -// ✅ Promotes `key` directly in-place without extra variables -for (final MapEntry(:key, :value) in map.entries) { - if (key is String && value != null) { - process(key, value); - } -} -``` - -#### 2. Consolidate Nullable Types in Switch Arms - -When mapping or returning values in a switch expression where both `null` and a -type `T` are valid and passed through, match the nullable type `T?` directly -rather than creating redundant `null` arms. - -**Avoid:** - -```dart -// ❌ Redundant separate null arm -switch (value) { - final String s => s, - null => null, - _ => throw FormatException(...), -} -``` - -**Prefer:** - -```dart -// ✅ Clean nullable pattern match -switch (value) { - final String? s => s, - _ => throw FormatException(...), -} -``` - -#### 3. Use `switch` Expressions or Inline `if-case` for Boolean Returns - -In Dart 3 grammar, ` case [when ]` is a `caseClause` -valid only inside `if (...)`, `for (...; ...; ...)`, or `while (...)` headers. -It is **not** a standalone boolean expression like `is`. To return a `bool` from -a pattern check, either inline `if (segments case [...])` at the branch site or -use a `switch` expression. - -**Avoid:** - -```dart -// ❌ Compile error: 'case' cannot be used as a standalone expression -bool isCommentsRoute(List segments) => - segments case ['api', 'comments', ...]; -``` - -**Prefer:** - -```dart -// ✅ Switch expression evaluates to a boolean value -bool isCommentsRoute(List segments) => switch (segments) { - ['api', 'comments', ...] => true, - _ => false, -}; -``` - -#### 4. Use `Set.contains` in `when` Guards over Long `||` Pattern Chains - -Each `||` logical-or pattern operator adds `+1` to cognitive complexity metrics -(e.g., `analytica.dart`). When checking single-element membership against an -existing `Set` or a large list of constant strings, bind the element in the list -pattern and check `allowedSet.contains(first)` in the `when` guard (or keep a -direct `Set.contains` check when no tail destructuring is needed). - -**Avoid:** - -```dart -// ❌ High cognitive complexity (+6 for 7 literals chained with ||) -if (segments case [ - 'status' || 'chat' || 'assets' || 'static' || 'api' || 'documents' || 'r', - ...final rest, -]) { - handleRoute(rest); -} -``` - -**Prefer:** - -```dart -// ✅ Binds head and tail cleanly with O(1) Set lookup in `when` -const allowedTopLevel = { - 'status', 'chat', 'assets', 'static', 'api', 'documents', 'r', -}; -if (segments case [final first, ...final rest] - when allowedTopLevel.contains(first)) { - handleRoute(rest); -} -``` - ## Related Skills +- **[dart-use-pattern-matching]**: Authoritative guide for Dart 3 pattern + matching, switch expressions, and list/map/`String.split()` destructuring. +- **[dart-seal-type-hierarchies]**: Converting closed class hierarchies into + `sealed` types for exhaustive switching. - **[dart-best-practices]**: General code style and foundational Dart idioms that predate or complement the modern syntax features. -- **[dart-use-pattern-matching]**: Comprehensive pattern matching, list/path - segment destructuring, and `String.split()` idioms. -[dart-best-practices]: - https://github.com/kevmoo/dash_skills/blob/main/skills/dart-best-practices/SKILL.md [dart-use-pattern-matching]: https://github.com/dart-lang/skills/blob/main/skills/dart-use-pattern-matching/SKILL.md +[dart-seal-type-hierarchies]: + https://github.com/kevmoo/dash_skills/blob/main/skills/dart-seal-type-hierarchies/SKILL.md +[dart-best-practices]: + https://github.com/kevmoo/dash_skills/blob/main/skills/dart-best-practices/SKILL.md