Linting y analysis_options en Flutter: ejercicio resuelto

Linting y analysis_options en Flutter: ejercicio resuelto

El analizador estático de Dart detecta errores, malas prácticas y código muerto antes de ejecutar la app. Un analysis_options.yaml bien configurado es la primera línea de defensa de calidad en cualquier proyecto profesional.

Enunciado

Configura analysis_options.yaml con:

  • Las reglas de flutter_lints como base.
  • Reglas adicionales de producción (prefer_final, avoid_print, etc.).
  • Errores de análisis tratados como errores de compilación.
  • Exclusiones para código generado.

Luego corrige los warnings más comunes en código Dart real.

Dependencias

1
2
3
4
dev_dependencies:
  flutter_lints: ^5.0.0
  # Alternativa más estricta (opcional):
  # very_good_analysis: ^7.0.0

Solución: analysis_options.yaml completo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# analysis_options.yaml (en la raíz del proyecto)
include: package:flutter_lints/flutter.yaml

analyzer:
  # Tratar warnings como errores (recomendado en CI)
  errors:
    invalid_annotation_target: ignore   # necesario con Freezed
    missing_required_param: error
    dead_code: warning
    unused_import: error
    unused_local_variable: warning

  exclude:
    - "**/*.g.dart"           # generado por build_runner / json_serializable
    - "**/*.freezed.dart"     # generado por Freezed
    - "lib/generated/**"      # i18n generado
    - "test/.test_coverage.dart"

linter:
  rules:
    # ── Calidad de código ──────────────────────────────────────────────────
    prefer_final_locals: true          # var → final cuando no se reasigna
    prefer_final_fields: true          # campos inmutables → final
    prefer_const_constructors: true    # usa const donde sea posible
    prefer_const_literals_to_create_immutables: true
    unnecessary_const: true
    unnecessary_new: true

    # ── Seguridad y bugs ───────────────────────────────────────────────────
    avoid_print: true                  # usa debugPrint o un logger
    avoid_dynamic_calls: true          # sin .call() en dynamic
    always_use_package_imports: false  # relativo OK en mismo package
    cancel_subscriptions: true         # StreamSubscription.cancel()
    close_sinks: true                  # Sink.close()
    unawaited_futures: true            # await todas las Futures relevantes

    # ── Estilo ────────────────────────────────────────────────────────────
    directives_ordering: true          # imports ordenados
    sort_pub_dependencies: false        # pub.dev los ordena automáticamente
    lines_longer_than_80_chars: false  # Dart fmt ya lo gestiona
    flutter_style_todos: true          # TODO(nombre): descripción

    # ── Nulabilidad ───────────────────────────────────────────────────────
    prefer_null_aware_operators: true
    avoid_null_checks_in_equality_operators: true
    null_closures: true

    # ── Rendimiento ───────────────────────────────────────────────────────
    avoid_unnecessary_containers: true  # sin Container innecesario
    use_colored_box: true               # Container(color:) → ColoredBox
    use_decorated_box: true             # Container(decoration:) → DecoratedBox

Ejemplos: código con warning y su corrección

1. avoid_print

1
2
3
4
5
6
7
8
9
// ❌ Warning: avoid_print
void fetchData() {
  print('Cargando datos...');
}

// ✅ Correcto
void fetchData() {
  debugPrint('Cargando datos...'); // solo en debug mode
}

2. prefer_final_locals

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// ❌ Warning: prefer_final_locals
void processUser(Map<String, dynamic> json) {
  var name = json['name'] as String;   // name nunca se reasigna
  var age = json['age'] as int;
  debugPrint('$name, $age');
}

// ✅ Correcto
void processUser(Map<String, dynamic> json) {
  final name = json['name'] as String;
  final age = json['age'] as int;
  debugPrint('$name, $age');
}

3. use_colored_box

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// ❌ Warning: use_colored_box
Widget build(BuildContext context) {
  return Container(
    color: Colors.red,
    child: const Text('Hola'),
  );
}

// ✅ Correcto (más eficiente, sin capa de layout extra)
Widget build(BuildContext context) {
  return const ColoredBox(
    color: Colors.red,
    child: Text('Hola'),
  );
}

4. unawaited_futures

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ❌ Warning: unawaited_futures
void onButtonTap() {
  saveToDatabase();   // Future no esperado
}

// ✅ Correcto
Future<void> onButtonTap() async {
  await saveToDatabase();
}

// ✅ Alternativa si es intencionado:
void onButtonTap() {
  unawaited(saveToDatabase()); // import 'dart:async'
}

5. cancel_subscriptions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// ❌ Warning: cancel_subscriptions
class _MyWidgetState extends State<MyWidget> {
  late StreamSubscription<int> _sub;

  @override
  void initState() {
    super.initState();
    _sub = stream.listen((_) {});
  }
  // dispose no cancela _sub → memory leak
}

// ✅ Correcto
@override
void dispose() {
  _sub.cancel();
  super.dispose();
}

Ejecutar el análisis desde la terminal

1
2
3
4
5
6
7
8
# Análisis completo del proyecto
dart analyze

# Solo errores (ignora warnings)
dart analyze --fatal-warnings

# Fix automático de problemas simples
dart fix --apply

Integración en CI/CD

1
2
3
4
5
6
# .github/workflows/ci.yml (fragmento)
- name: Analyze
  run: dart analyze --fatal-warnings

- name: Format check
  run: dart format --set-exit-if-changed .

very_good_analysis: cuándo usarlo

very_good_analysis es más estricto que flutter_lints y activa ~200 reglas. Es la opción que usa Very Good Ventures en proyectos de producción. Si estás arrancando un proyecto nuevo con estándares altos, empieza con él:

1
2
3
4
5
6
# pubspec.yaml
dev_dependencies:
  very_good_analysis: ^7.0.0

# analysis_options.yaml
include: package:very_good_analysis/analysis_options.yaml

Errores frecuentes

  • Ignorar todos los warnings con // ignore:: los ignore inline deberían ser la excepción, no la regla. Si un warning aparece en muchos sitios, evalúa si la regla es apropiada para tu proyecto y ajusta analysis_options.yaml.
  • No excluir archivos generados: *.g.dart y *.freezed.dart generan cientos de warnings falsos si no se excluyen.
  • Añadir --fatal-warnings en CI sin limpiar warnings primero: el pipeline falla desde el primer commit. Fija todos los warnings antes de activarlo.

Aplicación práctica

Un proyecto con linting estricto desde el día 1 detecta bugs antes de que lleguen a review, estandariza el estilo sin debates en PR y hace el onboarding más rápido porque el código es predecible.

Siguiente ejercicio recomendado

Práctica guiada y siguiente paso

FAQ

¿El linter ralentiza la compilación?

No. El análisis es estático y corre en paralelo al compilador. No impacta el tiempo de flutter run ni de flutter build.

¿Puedo desactivar una regla solo para un archivo?

Sí. Añade al inicio del archivo:

1
// ignore_for_file: avoid_print

O en una línea concreta:

1
print('debug'); // ignore: avoid_print

¿dart fix --apply es seguro?

Para la mayoría de correcciones automáticas sí — son equivalencias de código probadas. Revisa siempre el diff antes de hacer commit, especialmente en proyectos grandes.