dart-use-pattern-matching — скилл для Claude Code: паттерны Dart 3
Скилл задаёт агенту правила работы с паттернами Dart 3: switch-выражения, деструктуризация JSON, sealed-классы, сегменты пути.
npx skills add dart-lang/skills --skill dart-use-pattern-matching -a claude-codeЧто делает dart-use-pattern-matching
Скилл задаёт агенту правила работы с паттернами Dart 3. Внутри — разделы про выбор типа паттерна под структуру данных, разницу между switch-выражением и switch-инструкцией, синтаксис логических, реляционных, list, map, record и object-паттернов.
Перед правкой кода агент сверяется с таблицей условий. Полиморфный JSON разбирается через switch по ключу-дискриминатору в sealed-иерархию, сегменты uri.pathSegments и результаты String.split() — через list-паттерны с rest-элементом, несколько возвращаемых значений — через record-паттерны. Ручные проверки .length и арифметика индексов после этого уходят.
Самое полезное здесь — раздел про антипаттерны. Битые данные глушить нельзя: если элемент должен ронять разбор, if-case в цикле не подходит, нужна явная ошибка. Для проверки одного типа скилл советует is, а не if-case с лишним алиасом. Всё это работает на Dart 3, так что проверьте версию SDK в проекте перед тем, как просить агента переписать разбор ответа API.
Какие задачи решает
- разобрать полиморфный JSON в sealed-иерархию через switch по дискриминатору
- переписать длинную цепочку
if-elseна switch-выражение - разложить сегменты
uri.pathSegmentsбез арифметики индексов - достать несколько полей
MapEntryв локальные переменные - добиться полноты switch на sealed-классах
Примеры запросов агенту
- Разбери этот ответ API через switch-выражение и sealed-классы, скилл dart-use-pattern-matching.
- Перепиши разбор `uri.pathSegments` на list-паттерны с rest вместо проверок `.length`.
- Проверь мой `switch` на полноту по sealed-классу и убери лишние `null`-ветки.
Как установить dart-use-pattern-matching
1Через Skills CLI (Vercel)
npx skills add dart-lang/skills --skill dart-use-pattern-matching -a claude-code2Вручную: положить файлы
~/.claude/skills/dart-use-pattern-matching/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/dart-use-pattern-matching/
1Через Skills CLI (Vercel)
npx skills add dart-lang/skills --skill dart-use-pattern-matching -a codex2Вручную: положить файлы
.agents/skills/dart-use-pattern-matching/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add dart-lang/skills --skill dart-use-pattern-matching -a cursor2Вручную: положить файлы
.cursor/skills/dart-use-pattern-matching/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install dart-lang/skills dart-use-pattern-matching2Вручную: положить файлы
.github/skills/dart-use-pattern-matching/SKILL.md1Через Skills CLI (Vercel)
npx skills add dart-lang/skills --skill dart-use-pattern-matching -a gemini-cli2Командой платформы
gemini skills install https://github.com/dart-lang/skills --path skills/dart-use-pattern-matchingВручную: положить файлы
.devin/skills/dart-use-pattern-matching/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add dart-lang/skills --skill dart-use-pattern-matching -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
Из того же набора7
Весь набор →Похожие
Вопросы о dart-use-pattern-matching
Как установить dart-use-pattern-matching в Claude Code?
Выполните npx skills add dart-lang/skills --skill dart-use-pattern-matching -a claude-code. Скилл появится по пути ~/.claude/skills/dart-use-pattern-matching/SKILL.md.
Что делает dart-use-pattern-matching?
Описывает, когда и какой паттерн Dart 3 применить: switch-выражения, деструктуризация List, Map, Record, объектные паттерны на sealed-классах. Агент получает готовые правила выбора вместо общих рассуждений о синтаксисе.
Когда скилл не нужен?
Для простых булевых условий, повышения типа одной переменной и обычной фильтрации коллекций: там он прямо советует is вместо if-case.
Исходный текст
SKILL.md · BSD-3-Clause
---
name: dart-use-pattern-matching
description: >-
Applies Dart 3 pattern matching, switch expressions, and destructuring
idiomatically to validate data schemas, handle algebraic data types, and
decompose control flow. Use when refactoring complex if-else chains,
parsing polymorphic JSON or API responses, destructuring Lists, URI/path
segments, String.split() tokens, Records, or Maps, or enforcing exhaustiveness
on sealed classes. Don't use for simple boolean conditions, single-variable
type promotion (use `is`), or basic collection filtering.
metadata:
model: models/gemini-3.1-pro-preview
last_modified: Tue, 13 Oct 2026 19:45:00 GMT
---
# Implementing Dart Patterns
## Contents
- [Pattern Selection Strategy](#pattern-selection-strategy)
- [Switch Statements vs. Expressions](#switch-statements-vs-expressions)
- [Core Pattern Implementations](#core-pattern-implementations)
- [Pragmatic Balance & Anti-Patterns](#pragmatic-balance--anti-patterns)
- [Workflows](#workflows)
- [Examples](#examples)
## Pattern Selection Strategy
Apply specific pattern types based on the data structure and desired outcome. Follow these conditional guidelines:
* **If validating and extracting from deserialized data (e.g., JSON):** Use Map, List, and Object patterns to validate schema structure and destructure properties in a single step.
* **If inspecting URL/path segments (`uri.pathSegments`, `p.split(path)`) or `String.split()` tokens:** Use List patterns with rest elements (`['api', 'comments', ...]`, `['assets', ...final rest]`, `[..., final parent, _]`) instead of manual `.length` checks, `.first`, `.skip(1)`, or `length - N` index arithmetic.
* **If handling polymorphic payloads or responses:** Use `switch` expressions over map discriminant keys to deserialize into `sealed` class hierarchies.
* **If handling multiple return values:** Use Record patterns to destructure fields directly into local variables.
* **If unpacking multiple fields from an object or `MapEntry`:** Use Object patterns (`final Spec(:field1, :field2!) = spec;`, `for (final MapEntry(:key, :value) in map.entries)`) when promoting nullable properties or unpacking 3+ fields used across multiple statements, rather than for single-call passthroughs.
* **If executing type-specific behavior (Algebraic Data Types):** Use Object patterns combined with `sealed` classes to ensure exhaustiveness.
* **If matching numeric ranges or conditions:** Use Relational (`>=`, `<=`) and Logical-and (`&&`) patterns within switch arms.
* **If multiple cases share logic:** Use Logical-or (`||`) patterns to share a single case body or guard clause.
* **If ignoring specific values:** Use the Wildcard pattern (`_`) or a non-matching Rest element (`...`) in collections.
## Switch Statements vs. Expressions
Select the appropriate switch construct based on the execution context:
* **If producing a value:** Use a **switch expression**.
* Syntax: `switch (value) { pattern => expression, }`
* Rule: Each case must be a single expression. No implicit fallthrough. Must be exhaustive.
* **If executing statements or side effects:** Use a **switch statement**.
* Syntax: `switch (value) { case pattern: statements; }`
* Rule: Empty cases fall through to the next case. Non-empty cases implicitly break (no `break` keyword required).
## Core Pattern Implementations
Implement patterns using the following syntax and rules:
* **Logical-or (`||`):** `pattern1 || pattern2`. Both branches must define the exact same set of variables.
* **Logical-and (`&&`):** `pattern1 && pattern2`. Branches must *not* define overlapping variables.
* **Relational:** `==`, `!=`, `<`, `>`, `<=`, `>=` followed by a constant expression.
* **Cast (`as`):** `pattern as Type`. Throws if the value does not match the type. Use to forcibly assert types during destructuring.
* **Null-check (`?`):** `pattern?`. Fails the match if the value is null. Binds the variable to the non-nullable base type.
* **Null-assert (`!`):** `pattern!`. Throws if the value is null.
* **Variable:** `var name` or `Type name`. Binds the matched value to a new local variable.
* **Wildcard (`_`):** Matches any value and discards it.
* **List:** `[pattern1, pattern2]`. Matches lists of exact length unless a Rest element (`...` or `...var rest`) is used.
* **Map:** `{"key": pattern}`. Matches maps containing the specified keys. Ignores unmatched keys.
* **Record:** `(pattern1, named: pattern2)`. Matches records of the exact shape. Use `:var name` to infer the getter name.
* **Object:** `ClassName(field: pattern)`. Matches instances of `ClassName`. Use `:var field` to infer the getter name.
## Pragmatic Balance & Anti-Patterns
Pattern matching and switch expressions should simplify code, not add syntactic overhead. Observe the following boundaries:
### 1. Prefer `is` Type Promotion over `if-case` for Single Promotable Variables
When checking or promoting a single variable, use standard `is` checks instead of `if-case` patterns that introduce shadow aliases.
* **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);
}
}
```
* **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);
}
}
```
### 2. Consolidate Nullable Types in Switch Arms
When mapping or returning values where both `null` and a type `T` are valid and handled identically, match the nullable type `T?` directly rather than creating redundant `null` arms.
* **Prefer:**
```dart
// ✅ Clean nullable pattern match
switch (value) {
final String? s => s,
_ => throw FormatException('Invalid value: $value'),
}
```
* **Avoid:**
```dart
// ❌ Redundant separate null arm
switch (value) {
final String s => s,
null => null,
_ => throw FormatException('Invalid value: $value'),
}
```
### 3. Preserve Fast-Fail Validation (Do Not Silently Drop Data)
Do not use `if-case` in loops or deserialization to filter elements if malformed data should trigger an error or diagnostic warning.
* **Prefer:**
```dart
// ✅ Fast-fail with explicit diagnostic error
for (final raw in rawTasks) {
if (raw is! Map<String, dynamic>) {
throw FormatException('Expected Map item, got ${raw.runtimeType}: $raw');
}
_applyTask(raw);
}
```
* **Avoid:**
```dart
// ❌ Silently ignores malformed items
for (final raw in rawTasks) {
if (raw case final Map<String, dynamic> taskMap) {
_applyTask(taskMap);
}
}
```
### 4. Avoid Single-Case or Boolean Switches
* Use `if (x is T)` instead of a `switch` statement with only 1 case and `default: break;`.
* Use standard conditional ternary operators (`condition ? a : b`) instead of `switch (condition) { true => a, false => b }`.
### 5. Use Object Destructuring for Multi-Field Unpacking, Not Single Properties or Passthroughs
Use object or record destructuring when extracting 3+ fields used across multiple statements, iterating `MapEntry(:key, :value)` in `map.entries` loops, eliminating a single-use intermediate variable, or promoting nullable fields to local variables (`:field!` or `:field` with `!= null`). Keep standard property/element access (`config.field`) when:
* Reading a single property (`user.name`, or `final CpuCallFrame(:url, :line) = node.callFrame;` rather than nesting `ProfileNode(callFrame: ...)`).
* Unpacking only 1–2 fields in a short getter/method, or unpacking fields only to pass them straight into a single constructor or function call.
* Explicit local type annotations (`specify_nonobvious_local_variable_types`) require adding new library imports for marginal (1–2x) field reuse.
* A long class name with renamed getters wraps a 2-field unpack across 3+ lines.
* Any target variable is assigned to an outer-scoped variable (e.g., `db = res.$1;` before `finally` — `final (db, ...)` shadows outer `db`) or mixes `final` and mutable `var` locals.
* Reading `.first` and `.last` on a list that may have length 1 (`[first, ..., last]` requires `length >= 2`; do not replace with multi-branch `if-case`).
* **Prefer:**
```dart
// ✅ Unpacks multiple fields and asserts non-nullability in one declaration
final RunSpec(:target, :device!, :verbose) = spec;
```
* **Avoid:**
```dart
// ❌ Repetitive multi-line property extraction
final target = spec.target;
final device = spec.device!;
final verbose = spec.verbose;
```
### 6. Avoid `if-case` for Standalone Scalar Comparisons
Use standard boolean operators (`if (code >= 200 && code < 300)`) instead of `if (code case >= 200 && < 300)` for standalone conditions. Reserve relational patterns for multi-arm `switch` tables.
## Workflows
### Task Progress: Implementing Pattern Matching
Copy this checklist to track progress when implementing complex pattern matching logic:
- [ ] Identify the data structure being evaluated (JSON, List/Segments, Record, Class, Enum).
- [ ] Select the appropriate switch construct (Expression for values, Statement for side-effects).
- [ ] Define the required patterns (Object, Map, List, Record).
- [ ] Extract required data using Variable patterns (`var x`, `:var y`, `...final rest`).
- [ ] Apply Guard clauses (`when condition`) for logic that cannot be expressed via patterns.
- [ ] Handle unmatched cases using a Wildcard (`_`) or `default` clause (if not using a sealed class).
- [ ] Run static analyzer for exhaustiveness and dead code (`dart analyze`).
- [ ] Verify runtime Map/JSON pattern behavior on omitted keys (`containsKey` semantics) vs explicit `null` values.
### Feedback Loop 1: Exhaustiveness Checking (Static Verification)
When switching over `sealed` classes or enums, ensure all subtypes are handled at compile time:
1. **Run analyzer:** Execute `dart analyze`.
2. **Review errors:** Look for "The type 'X' is not exhaustively matched by the switch cases" or unreachable pattern arm warnings.
3. **Fix:** Add the missing Object patterns for unhandled subtypes, or add an explicit wildcard (`_`) arm if a default fallback or error is acceptable.
### Feedback Loop 2: Runtime Map & JSON Pattern Verification
Because `dart analyze` cannot statically verify dynamic `Map<String, dynamic>` keys, validate runtime pattern semantics explicitly:
1. **Omitted Keys vs. Explicit `null`**: A map pattern `{'key': String? val}` checks `map.containsKey('key')`. If `'key'` is omitted from the JSON payload, the pattern fails to match at runtime even though `String?` is nullable. Extract optional keys from the validated map directly (`map['key'] as String?`).
2. **Fast-Fail Fallback**: Ensure unmatched or malformed map structures hit an explicit `_ => throw FormatException(...)` arm rather than silently failing an `if-case` check.
## Examples
### Polymorphic JSON Deserialization (Discriminated Unions)
Use Map patterns with switch expressions to validate tagged JSON payloads and
construct `sealed` class hierarchies. See
[examples/json_patterns.dart](examples/json_patterns.dart) for an executable
implementation demonstrating tagged `ApiResponse` parsing into `SuccessResponse`
and `ErrorResponse`.
### Nested JSON Validation and Optional Fields
Use nested Map and List patterns to validate required schema structure and
extract collections in a single step. See
[examples/json_patterns.dart](examples/json_patterns.dart) for an executable
implementation of `processUserPayload`.
Map patterns check for key existence (`containsKey`). If an optional JSON key
might be omitted entirely from the payload (rather than explicitly passed as
`'key': null`), destructure required keys via the pattern and extract optional
fields directly from the matched submap.
### List and Path Segment Destructuring
Use `if-case` with list rest elements (`...final rest`) to validate prefixes and
extract remaining elements without a 2-arm `_ => null` switch or manual `.first`
and `.skip(1)` indexing.
```dart
String? resolveAllowedAssetSubpath(List<String> segments) {
if (segments case ['assets', ...final rest]
when rest.isNotEmpty && !rest.contains('..')) {
return rest.join('/');
}
return null;
}
```
### Algebraic Data Types (Sealed Classes)
Use Object patterns with switch expressions to handle family types exhaustively.
```dart
sealed class Shape {}
class Square implements Shape {
final double length;
Square(this.length);
}
class Circle implements Shape {
final double radius;
Circle(this.radius);
}
// Switch expression guarantees exhaustiveness due to `sealed` modifier.
double calculateArea(Shape shape) => switch (shape) {
Square(length: var l) => l * l,
Circle(:var radius) => math.pi * radius * radius,
};
```
### Variable Swapping and Destructuring
Use variable assignment patterns to swap values or extract record fields without temporary variables.
```dart
var (a, b) = ('left', 'right');
(b, a) = (a, b); // Swap values
// Destructuring a function return
var (name, age) = getUserInfo();
```
### Guard Clauses and Logical-or
Use `when` to evaluate arbitrary conditions after a pattern matches.
```dart
switch (shape) {
case Square(length: var s) || Circle(radius: var s) when s > 0:
print('Valid positive shape with dimension $s');
case Square() || Circle():
print('Zero or negative dimension shape');
}
```
© dart-lang · BSD-3-Clause · dart-lang/skills