Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ npx skills add kevmoo/dash_skills --skill <skill-name>
| **[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 |
Expand Down
287 changes: 41 additions & 246 deletions skills/dart-modern-features/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand All @@ -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:
Expand Down Expand Up @@ -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<String, dynamic> 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<String, dynamic> 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
Expand Down Expand Up @@ -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, `<expr> case <pattern> [when <guard>]` 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<String> segments) =>
segments case ['api', 'comments', ...];
```

**Prefer:**

```dart
// ✅ Switch expression evaluates to a boolean value
bool isCommentsRoute(List<String> 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
Loading