Refactor Dart Dot Shorthands
Apply Dart 3.7+ dot shorthands (member access shorthands) to simplify code where the context type is already known.
Constraints
- Single Package Focus: Only apply changes to a single package in
pkgs/at a time. Do not spread changes across multiple packages in a single operation. - CI Usage: Only run
dart tool/ci.dart --all --fastif changes are made to packages that are part of the top-level pub workspace (as defined in the rootpubspec.yaml). If the package is not in the workspace, use local validation (dart analyze,dart test) within that package's directory. - No
.newShorthand: Do not use.newas a shorthand for constructors (e.g., keepMyClass()orMyClass.new()if explicit, but do not refactor to just.new()). - Immediate Clarity: Only use dot shorthands if the type is immediately clear from the line itself (e.g., in a typed variable declaration, a switch on a variable with a clear type, or a collection with an explicit type argument). Do not use them if identifying the type requires scrolling or searching elsewhere in the file.
- No Explicit Typing for Shorthands: DO NOT add explicit type arguments to collections (e.g., changing
const x = [...]toconst x = <Type>[...]) or explicit types to variables just to enable the use of dot shorthands. Shorthands should only be used where the context type is already explicitly defined in the existing code. - Variable/Parameter Naming: Only use shorthands if the variable or parameter name contains the type name (or a very clear abbreviation). Avoid renaming variables or parameters solely to enable shorthands, especially if they are part of a public API or used in many locations.
- Avoid Complex Contexts: Avoid using shorthands in complex contexts where inference might be brittle, such as inside nested tuples in switch statements, unless the analysis tool confirms it's valid.
- Maintain Formatting: When refactoring lists or collections, preserve the original formatting (e.g., multiline lists with trailing commas). Do not squash them into a single line.
- No Redundant Shorthands: Do not use dot shorthands if the member is already accessible without any qualifier (e.g., inside the class where the static member is defined). Prefer
memberover.memberif both work. - Iterative Progress Tracking: You MUST update
SHORTHAND_PROGRESS.mdafter processing each file (or a small batch of no more than 3 files). This ensures progress is visible and prevents losing work if the session is interrupted. Do not wait until the end of the package to update the progress file.
Workflow
- Select Target Package: Choose a single package in
pkgs/to refactor. - Initialize Progress:
- Recursively list all
.dartfiles in the package. - Create a temporary markdown file (e.g.,
SHORTHAND_PROGRESS.md) containing a checklist of all files.
- Recursively list all
- Iterative Refactoring: Work through the files in small batches (1-3 files):
- Identify: Search for shorthand opportunities within the files that meet the Constraints.
- Apply: Replace
TypeName.memberNamewith.memberName. - Validate: Run
dart analyzeon the affected files/package. - Record: Immediately update the checklist in
SHORTHAND_PROGRESS.mdto mark the processed files as completed. Do not skip this step.
- Final Verification:
- Once all files are processed, perform the final validation step as described in the Verification section.
- Cleanup: Delete the temporary progress file.
Examples
Arguments and named parameter default values
// Before
super.language = Language.c,
// After
super.language = .c,
Only do the refactoring if the receiver or parameter name contains the type modulo spacing.
// Before
super(type: OutputType.library);
// After, bad. (Type not clear from 'type' parameter)
super(type: .library);
If not part of the public API, consider renaming the named parameter to match the type name.
// After
super(outputType: .library);
However, only do this if the code quality doesn't suffer.
Collections and Lists
Maintain multiline formatting for better readability.
// Before
const targets = [
Architecture.arm,
Architecture.arm64,
Architecture.ia32,
Architecture.x64,
];
// After
const targets = <Architecture>[
.arm,
.arm64,
.ia32,
.x64,
];
Variable assignments and function calls
// Before
logger.level = Level.INFO;
// After
logger.level = .INFO;
If the type is contained in the name it is fine:
// Before
hostOS = hostOS ?? OS.current,
// After
hostOS = hostOS ?? .current,
Avoid shorthands if the context is lost:
// Before
await expectMachineArchitecture(libUri, target, OS.android);
// After, bad. (What is .android? Architecture? OS?)
await expectMachineArchitecture(libUri, target, .android);
Equality checks
// Before
'/INCLUDE:${targetArch == Architecture.ia32 ? '_' : ''}$symbol'
// After, not great
'/INCLUDE:${targetArch == .ia32 ? '_' : ''}$symbol'
If variable names are not part of the public API, refactor variable name to match the type name.
// After
'/INCLUDE:${targetArchitecture == .ia32 ? '_' : ''}$symbol'
However only do this if it keeps the code understandable.
Note: Shorthands are most effective for enums and static constants used as values.
Verification
After applying changes, run the following in the package directory:
dart analyze
If the package is part of the top-level workspace, run from the project root:
dart tool/ci.dart --all --fast
Otherwise, run tests locally in the package directory:
dart test