skill: add dart skills

This commit is contained in:
2026-07-14 10:42:05 +08:00
parent c1c0a3abfc
commit 6c07b17524
13 changed files with 2634 additions and 0 deletions

View File

@@ -0,0 +1,262 @@
---
name: dart-use-primary-constructors
description: >
Help users write syntactically and semantically correct primary constructors in Dart, and migrate/use the new constructor syntax, empty-body semicolon syntax, in-body initializer list syntax, and abbreviated concise constructor syntax.
metadata:
model: models/gemini-3.1-pro-preview
last_modified: Thu, 09 Jul 2026 23:13:25 GMT
---
# Dart Primary Constructors & New Constructor Syntax Skill
Use this skill when helping users write, refactor, or debug code using Dart's **Primary Constructors** feature.
### Dart Version Requirements
* **Dart 3.13 and above**: Primary constructors are enabled by default.
* **Dart 3.12**: The feature is available but experimental. Users must explicitly enable the experiment flag `primary-constructors` via `--enable-experiment=primary-constructors` or in `analysis_options.yaml`:
```yaml
analyzer:
enable-experiment:
- primary-constructors
```
* **Dart 3.11 and earlier**: Primary constructors are not supported.
---
## 1. Overview
Primary Constructors allow developers to declare a non-redirecting generative constructor as well as a set of instance variables directly in the class header. This significantly reduces boilerplate and improves code readability.
### Key Benefits
- Combines field declaration, parameter declaration, and initialization into a single declaration known as a declaring parameter declaration.
- Enables safe reference to constructor parameters in non-late field initializers (Primary Initializer Scope).
- Allows empty declaration bodies to be represented concisely with a semicolon (`;`).
- Introduces abbreviated concise syntax for in-body constructors.
---
## 2. Syntax Reference
### 2.1 Basic Class Header Syntax
To declare a primary constructor, place a parameter list immediately after the type name (and optional type parameters):
```dart
// Declares fields x and y, and a generative constructor Point(this.x, this.y)
class Point(var int x, var int y);
// Declares final fields
class PointFinal(final int x, final int y);
```
### 2.2 Declaring, Initializing, and Plain Parameters
A primary constructor parameter list distinguishes between three types of parameters:
1. **Declaring Parameters**: Indicated by the `var` or `final` modifier (e.g., `final int x`). They implicitly create a corresponding instance field in the class.
2. **Initializing Parameters**: Indicated by the `this.` or `super.` prefix (e.g., `this.x` or `super.x`). They initialize an existing field or a super constructor parameter, respectively.
3. **Regular Parameters**: Declared without modifiers (e.g., `int y`). They do not become fields and are only available during initialization (e.g., in field initializers or the `this :` initializer list in the class body).
```dart
// `x` is a field and a parameter because it has the keyword `final`. In particular, we can use the name `x` in the initializer list in the in-body part of the primary constructor. 'y' is a only parameter because it has neither of the keywords `final` or `var`, but `y` is passed to the super constructor via the `this :` initializer list.
class C(final int x, int y) extends Base {
this : super(y);
}
```
Declaring parameters and initializing parameters are two ways of achieving the same goal: declaring a class with instance fields which are set in the constructor. Regular parameters are different in that their values are not automatically routed to an instance field.
### 2.3 Constant Primary Constructors
To make a primary constructor `const`, place the `const` keyword before the class/type name in the declaration header:
```dart
class const Point(final int x, final int y);
extension type const Ext(int x);
enum const MyEnum(final int x) {
entry(1);
}
```
### 2.4 Extension Types
Extension types **must** use primary constructors.
- The single parameter in the header is the representation field.
- The representation variable cannot use the `var` modifier (using `var` triggers the `representation_field_modifier` error).
- The representation variable can optionally use the `final` modifier. If `final` is not present then it is inferred; that is, the parameter is declaring whether or not it's explicitly `final`.
### 2.5 Empty Body Semicolon Shorthand (`;`)
When a class, mixin class, mixin, extension or extension type has an empty body, the `{}` braces can be replaced by a semicolon (`;`):
```dart
class C(int x);
mixin class MC;
extension type ET(int x);
mixin M;
extension Ext on C;
```
### 2.6 The In-Body Part of a Primary Constructor (`this ...`)
If a primary constructor requires assertions or custom field initializations, they can be declared in the body using the `this :` syntax:
```dart
class Point(var int x, var int y) {
// Initializer list in class body
this : assert(x >= 0), y = y * 2;
}
```
You can also write a constructor body with this syntax (`this {...}`).
### 2.7 Abbreviated Concise Constructor Syntax
For constructors declared within the class body, the class name can be omitted and replaced with the `new` or `factory` keywords:
| Traditional Syntax | Abbreviated Concise Syntax |
| :--- | :--- |
| `MyClass() {}` | `new() {}` |
| `MyClass.name() {}` | `new name() {}` |
| `const MyClass();` | `const new();` |
| `const MyClass.name();` | `const new name();` |
| `factory MyClass() => ...` | `factory() => ...` |
| `factory MyClass.name() => ...` | `factory name() => ...` |
---
## 3. Semantics & Scoping Rules
### 3.1 Primary Initializer Scope
When a primary constructor is declared, its formal parameters are introduced into the **Primary Initializer Scope**. This scope is the current scope for non-late field initializers in the class body and the primary constructor's initializer list (after `this :`).
This allows non-late fields to reference constructor parameters directly during declaration:
```dart
class DeltaPoint(final int x, int delta) {
// 'x' and 'delta' are in scope here
final int y = x + delta;
}
```
### 3.2 Late Instance Variables Restriction
The primary initializer scope is **not** active for `late` instance variable initializers.
- Since `late` variables can be evaluated after construction has completed, their initializers cannot safely access constructor parameters.
- Attempting to access a primary constructor parameter in a `late` field initializer results in a compile-time error.
### 3.3 Shadowing
Primary constructor parameters shadow class members (fields) of the same name within the primary initializer scope:
- In a non-late initializer: `int y = x` refers to parameter `x`.
- In a `late` initializer: `late int y = x` refers to field `x` (if it exists) because the parameter `x` is out of scope.
### 3.4 Generative Constructor Restrictions
To guarantee that the primary constructor (and the associated initializer scope) always executes:
- A class, mixin class, or enum declaration with a primary constructor **cannot** declare any other non-redirecting generative constructors (except extension types).
- All other generative constructors declared in the body **must** redirect (directly or indirectly) to the primary constructor.
### 3.5 Parameter Mutation Errors
Primary constructor parameters are non-assignable inside the initialization phase.
- Any assignment to a parameter (e.g., `p = value`, `p++`) inside field initializers or the `this :` initializer list is a compile-time error.
### 3.6 Double Initialization Errors
Initializing a field twice (e.g., once in the field declaration/initializer and once in the `this :` initializer list or as an initializing formal) is a compile-time error.
---
## 4. Diagnostics & Troubleshooting
Most errors and lints have quick-fixes, run `dart fix` to fix those violations. For other common errors, fix them using the following table:
| Error / Lint Code | Common Cause | Resolution |
| :--- | :--- | :--- |
| **Invalid Late Access** | Referencing a primary constructor parameter inside a `late` field initializer. | Make the field non-late, or pass the value through another non-late field. |
| `fieldInitializedInInitializerAndDeclaration` | Initializing a variable both in its declaration and in the `this :` list. | Remove one of the initializations. |
| `nonRedirectingGenerativeConstructorWithPrimary` | Declaring a in-body generative constructor in the body without redirecting to the primary. | Change the in-body constructor such that it is redirecting (e.g. `this(...)`) or remove the in-body constructor. |
---
## 5. Step-by-Step Refactoring Workflows
### Workflow 5.1: Migrating a Class to a Primary Constructor
Follow these steps to migrate a verbose class to the new primary constructor syntax:
1. **Identify Candidate Fields and Constructor**:
Locate generative constructors and the fields they initialize. In this case, this would be the `name` and `age` fields.
```dart
// Before
class User {
final String name;
final int age;
User(this.name, this.age);
}
```
2. **Move Fields to the Header**:
Place fields in the header with `final` or `var` modifiers and append a semicolon (`;`) if the body is empty. The `name` and `age` fields are now written the primary constructor as declaring parameters `final String name` and `final int age`, respectively.
```dart
// After
class User(final String name, final int age);
```
3. **Handle Custom Initializers and Assertions**:
If there is an initializer list or assert block, move it to a `this` block inside the body:
```dart
// Before
class Point {
final int x;
final int y;
Point(this.x, this.y) : assert(x >= 0);
}
// After
class Point(final int x, final int y) {
this : assert(x >= 0);
}
```
4. **Leverage Primary Initializer Scope for Calculations**:
If a field value is calculated from parameters, declare it inside the body and assign it directly using the parameters:
```dart
// Before
class Rect {
final double width;
final double height;
final double area;
Rect(this.width, this.height) : area = width * height;
}
// After
class Rect(final double width, final double height) {
// 'width' and 'height' are in scope here
final double area = width * height;
}
```
5. **Convert In-Body Constructors to Redirecting**:
Ensure all in-body generative constructors redirect to the primary constructor:
```dart
// Before
class Point {
final int x;
final int y;
Point(this.x, this.y);
Point.zero() : x = 0, y = 0;
}
// After
class Point(final int x, final int y) {
new zero() : this(0, 0); // Redirects to primary
}
```
### Workflow 5.2: Applying Abbreviated (Concise) In-Body Constructors
When the user prefers to keep the constructor in the class body but wants to reduce verbosity, suggest the abbreviated constructor syntax:
```dart
// Before
class DatabaseService {
final String url;
DatabaseService(this.url);
DatabaseService.local() : url = 'localhost';
factory DatabaseService.create() => DatabaseService('default');
}
// After
class DatabaseService {
final String url;
new(this.url); // Omit class name, use 'new'
new local() : url = 'localhost'; // Use 'new local' for named constructors
factory create() => DatabaseService('default'); // Omit class name from factory
}
```