Cuando trabajas en proyectos de Dart que crecen o que deben actualizarse a nuevas versiones del SDK, la limpieza del código puede volverse una tarea titánica. Aquí es donde entran dart fix y dart migrate.
dart fix es una herramienta de mantenimiento diseñada para automatizar la corrección de problemas menores detectados por el linter. No es un linter en sí mismo, sino un ejecutor de las “quick fixes” (correcciones rápidas) que las reglas de análisis ya han identificado. Si una regla de analysis_options.yaml tiene una solución implementada, dart fix puede escribir el código por ti.
Por otro lado, dart migrate es una herramienta de migración de alto nivel, diseñada específicamente para el salto histórico hacia el Sound Null Safety [disponible desde Dart 2.12]. A diferencia de dart fix, que trata con reglas de estilo y APIs obsoletas, migrate realiza un análisis profundo del flujo de tipos para inferir dónde deben ir los signos de interrogación (?) para tipos nulos, los signos de exclamación (!) para asegurar la no-nulidad, o la palabra clave late cuando la inicialización es tardía.
Debes usar dart fix --dry-run siempre antes de aplicar cambios para ver exactamente qué transformaciones hará el compilador en tu AST (Abstract Syntax Tree). Usa dart fix --apply solo cuando quieras limpiar de golpe avisos de deprecación o falta de const. Reserva dart migrate exclusivamente para proyectos legacy que aún no utilizan el sistema de tipos de seguridad de nulos. Si intentas usar migrate en un código que ya es null-safe, la herramienta no encontrará nada que transformar.
Si usas dart fix --apply sin revisar el dry-run, podrías encontrarte con cambios en cascada que, aunque correctos sintácticamente, alteran la intención de tu lógica en casos muy complejos. Si usas dart migrate y aplicas las sugerencias sin revisar manualmente la sección de “bang operators” (!), podrías introducir errores de ejecución (runtime errors) donde el compilador se siente forzado a aceptar un valor que en realidad podría ser nulo en tiempo de ejecución.
// Ejemplo de código que requiere intervención de dart fix
import 'dart:async';
class ServidorConfig {
final String url;
final int timeout;
// Falta 'const' en el constructor y 'required' en los parámetros
ServidorConfig({this.url, this.timeout});
}
Future<void> conectar() async {
// Falta el 'await' para la función asíncrona
iniciarConexion();
print('Conectado');
}
Future<void> iniciarConexion() async {
await Future.delayed(const Duration(seconds: 2));
}
void main() {
// Uso de 'new' (obsoleto en Dart 3) y falta de 'const'
var config = new ServidorConfig(url: 'https://api.ejemplo.com', timeout: 30);
print('Configuración: ${config.url}');
conectar();
}
En el ejemplo anterior, dart fix actuaría sobre varios puntos críticos. Primero, al detectar la falta de const en ServidorConfig y en la instancia de Config, la herramienta añadiría la palabra clave const donde sea posible para optimizar la memoria y el rendimiento en tiempo de ejecución, permitiendo que el compilador AOT reutilice la instancia.
En la clase ServidorConfig, si tenemos activada la regla required_param, dart fix añadiría el modificador required a los parámetros del constructor, evitando que el compilador permita instanciar la clase con valores nulos inesperados. En la función conectar, la herramienta detectaría que iniciarConexion() devuelve un Future que no está siendo esperado (basado en la regla unnecessary_async o similar según la configuración), y añadiría el await necesario para asegurar que la ejecución sea secuencial. Finalmente, eliminaría la palabra clave new en main, ya que desde Dart 2.x (y totalmente integrada en Dart 3) ya no es necesaria y su uso se considera ruido visual.
El error frecuente
El error más peligroso ocurre durante el proceso de dart migrate. El flujo correcto es:
1. Ejecutar dart pub upgrade --null-safety.
2. Ejecutar dart migrate.
3. Revisar la interfaz web que se abre localmente.
4. Aplicar los cambios.
El problema ocurre cuando, al revisar la interfaz web, el desarrollador acepta automáticamente las sugerencias donde la herramienta ha colocado un operador ! (bang operator). dart migrate es muy bueno infiriendo tipos, pero si hay una parte de tu código donde la lógica es ambigua, la herramienta preferirá usar ! para que el código compile. Si aplicas esto sin mirar, estarás diciendo al compilador: “Confía en mí, esto nunca será nulo”, cuando en realidad podría serlo, lo que resultará en un TypeError catastrófico en producción.
N° 101