【Flutter】Linterのルールを読んで学んだこと

# はじめに
FlutterでCIを行いたいと思い、導入方法を調べていました。（その時の記事は[こちら](https://qiita.com/MLLB/items/055499e07351b4e89e75)）

CIの中で、静的解析を実行するようにしたのですが、効果的に静的解析をするためにはデフォルトの`analysis_options.yaml`をカスタマイズする必要があります。
Flutterのプロジェクト作成時に`analysis_options.yaml`は自動的に作成されますが、デフォルトの内容だと設定が緩いため、もう少し厳しめに設定した方がチームでコーディングのスタイルが統一しやすくなると思いました。


前置きが長くなりましたが、本記事では、`analysis_options.yaml`をカスタマイズするにあたり、[Linterのルール一覧](https://dart.dev/tools/linter-rules)を読んで、知らなかったコードの書き方や文法、Widgetがあったので、学んだことを記載します。

# analysis_options.yamlとは

備忘録として、`analysis_options.yaml`についても調べたことを整理したいと思います。
知っている方は、[Linterのルールを読んで学んだこと](#linterのルールを読んで学んだこと)スキップしてください。

`analysis_options.yaml`は、コードを静的解析するための設定ファイルです。
静的解析の目的は、コードを実行する前に、記述ミス（閉じカッコがないとか）によるバグや、ソースコードの書き方に違反があったら警告することです。
ソースコードの書き方の違反とは、例えば、以下のようなイメージです。
- 文字列を表す場合はダブルクオートとするというルールがあったとすると、シングルクオートを使っていたら警告する
- `final`を使える場面で`var`として変数定義をしていたら、`final`を使うように警告する

## analysis_options.yamlの構成
`analysis_options.yaml`は三つの構成があります。
- include
- analyzer
- linter

### include

すでに定義されている設定を取り込むことができます。
デフォルトだと、`include: package:flutter_lints/flutter.yaml`と設定されています。

他の人が作った設定を取り込むこともできて、以下の記事内でおすすめの設定が紹介されているのでご参照ください。
特にこだわりがない場合は、おすすめの設定を取り込んでベースにするのがいいと思います。

https://tech-blog.rakus.co.jp/entry/20210518/flutter

https://zenn.dev/heyhey1028/scraps/59a4f92cf9418e


### analyzer

静的分析をカスタマイズする部分になります。
カスタマイズできるのは下記です。
- 厳密な型チェックを有効にする
- 特定のファイルをチェックの対象外とする
- 特定のルールを無視する
- ルールの重要度を変更する

#### 厳密な型チェックを有効にする
Dartには、デフォルトで型チェックをするようになっていますが、その設定をより厳しくすることができるようです。

https://dart.dev/tools/analysis#enabling-additional-type-checks


#### 特定のファイルをチェックの対象外とする
以下のように、`exclude`にパスを定義することで、チェックの対象外とすることができます。

```yaml
analyzer:
  exclude:
    - lib/client.dart
    - lib/server/*.g.dart
    - test/_data/**
```

#### 特定のルールを無視する

以下のように特定のルールを無視するように設定できます。

```yaml
analyzer: 
  errors: 
    invalid_annotation_target: ignore
```

例えば、Freezedを使っていると、`@JsonSerializable`で警告が出るので、上記の設定のように無視するよう設定できます。

https://naokirin.hatenablog.com/entry/2022/10/16/151046


#### ルールの重要度を変更する

以下のように、ルールごとに警告のレベルを設定できます。

```yaml
analyzer:
  errors:
    invalid_assignment: warning
    missing_return: error
    dead_code: info
```

以下の記事の説明がわかりやすかったです。

https://makicamel.hatenablog.com/entry/2019/04/09/234024

ルールとして設定できるのは、以下の二つのリストみたいでした。

https://dart.dev/tools/diagnostic-messages

https://dart.dev/tools/linter-rules

### linter
linterのルールを個別に有効・無効の設定する部分になります。
要はこの書き方はOKで、この書き方はNGを設定していく部分です。
例えば、以下のルールがあります。
- 文字列はダブルクオートで定義する
- パッケージのインポートを相対パスで書かないようにする

何が使用できるかは以下にすべてまとまってます。

https://dart.dev/tools/linter-rules

書き方は以下の二つがあるみたいです。

#### 有効にするルールを定義する方法
```yaml
linter:
  rules:
    - always_declare_return_types
    - cancel_subscriptions
    - close_sinks
```

#### ルールごとに有効(true)と無効(false)を定義する方法
```yaml
linter:
  rules:
    avoid_shadowing_type_parameters: false
    await_only_futures: true
```

# Linterのルールを読んで学んだこと

記事の本題ですが、Linterのルールを読んで学んだことを書いていきます。

## prefer_is_not_operator

https://dart.dev/tools/linter-rules/prefer_is_not_operator

変数が指定の型かどうかを判定する時、`if(str is String)`で判定できますが、指定の型ではないという判定をしたい時、`is!`で判定できるみたいです。

```dart
final str = "aaaa";
if(str is! int){
  print("str is not int");
}
```

## prefer_iterable_whereType
https://dart.dev/tools/linter-rules/prefer_iterable_whereType

Listなどで、指定の型だけの要素を抽出したい場合、`where`ではなく`whereType`を使えます

```dart
// 下記は同じ結果になる
iterable.where((e) => e is MyClass);

iterable.whereType<MyClass>();
```

## sized_box_shrink_expand

https://dart.dev/tools/linter-rules/sized_box_shrink_expand

SizedBoxの書き方に関して、以下のように書き方ができるみたいです。
```dart
Widget buildLogo() {
  return SizedBox(
    height: 0,
    width: 0,
    child: const MyLogo(),
  );
}

// 上記は以下の書き方ができる
Widget buildLogo() {
  return SizedBox.shrink(
    child: const MyLogo(),
  );
}
```

```dart
Widget buildLogo() {
  return SizedBox(
    height: double.infinity,
    width: double.infinity,
    child: const MyLogo(),
  );
}

// 上記は以下の書き方ができる
Widget buildLogo() {
  return SizedBox.expand(
    child: const MyLogo(),
  );
}
```

## type_literal_in_constant_pattern

https://dart.dev/tools/linter-rules/type_literal_in_constant_pattern

例には以下のようなコードが書かれていて、`if (x case num)`という書き方を初めて見たので、調べました。

```dart
// BAD
void f(Object? x) {
  if (x case num) {
    print('int or double');
  }
}
// GOOD
void f(Object? x) {
  if (x case num _) {
    print('int or double');
  }
}

// BAD
void f(Object? x) {
  if (x case int) {
    print('int');
  }
}

// GOOD
void f(Object? x) {
  if (x case const (int)) {
    print('int');
  }
}
```

` if (x case num _)`に注目します。
まずnumについては、intとdoubleの継承元でした。
つまり、Dart標準の型です。

https://api.flutter.dev/flutter/dart-core/num-class.html

わかっている情報は、xは変数、numは型となりました。

caseは何かというと、`if-case`という文法みたいです。

https://dart.dev/language/branches#if-case

パターンにマッチすると、ifの{}内で、定義した変数が使えます。（`if (x case num _)`の`_`の部分が変数）
わかりやすく書き直すと下記になります。

```dart
final i = 0;
if(i case num number){
  // iがnum型なら、このスコープでnumberという変数として扱える
  print(number);
  print("i is num");
}
```

## unawaited_futures

https://dart.dev/tools/linter-rules/unawaited_futures

こちらのルールの例文で、`unawaited`というメソッドを知りました。
Futureのメソッドを明示的に待たないことを表せるメソッドみたいです。
上記のルールは、明示的に待たない部分は、unawaitedを使うというルールになります。
個人的に、有用なルールだと思いました。
実際のプロジェクトで、awaitが必要なところでawaitを書き忘れていて、バグとなった経験があるので、設定しておきたいルールです。

```dart
void main() async {
  await futurePrint();
  print("await futurePrint");
  unawaited(futurePrint());
  print("unwaited futurePrint");

  // 実行すると下記の順番になる
  // futurePrint
  // await futurePrint
  // unwaited futurePrint
  // futurePrint
}

Future<void> futurePrint() async {
  await Future.delayed(const Duration(milliseconds: 500));
  print("futurePrint");
}
```

## unnecessary_lambdas

https://dart.dev/tools/linter-rules/unnecessary_lambdas

以下のようにGOODの方を書くようにするルールですが、forEachにメソッドを渡す書き方は見たことがなかったので、短縮できていいなと思いました。

```dart
// BAD
names.forEach((name) {
  print(name);
});

// GOOD
names.forEach(print);
```

上手いコード例が見つからなかったのですが、下記のように書けるという感じです。
```dart
void main() {
  final numStrList = ["1", "2", "3"];
  // final numList = numStrList.map((e) => toInt(e)).toList();
  final numList = numStrList.map(toInt).toList();
}
    
int toInt(String str) {
  return int.parse(str);
}
```

## use_colored_box

https://dart.dev/tools/linter-rules/use_colored_box

以下のコード例のように、色だけを指定する`Container`の場合、`ColoredBox`を使う方が良いみたいです。

```dart
// BAD
Widget buildArea() {
  return Container(
    color: Colors.blue,
    child: const Text('hello'),
  );
}

// GOOD
Widget buildArea() {
  return const ColoredBox(
    color: Colors.blue,
    child: Text('hello'),
  );
}
```

## use_decorated_box

https://dart.dev/tools/linter-rules/use_decorated_box

`use_colored_box`と同じような感じで、decorationだけを指定する`Container`の場合、`DecoratedBox`を使用するほうがいいみたいです。

```dart
// BAD
Widget buildArea() {
  return Container(
    decoration: const BoxDecoration(
      color: Colors.blue,
      borderRadius: BorderRadius.all(
        Radius.circular(5),
      ),
    ),
    child: const Text('...'),
  );
}

// GOOD
Widget buildArea() {
  return const DecoratedBox(
    decoration: BoxDecoration(
      color: Colors.blue,
      borderRadius: BorderRadius.all(
        Radius.circular(5),
      ),
    ),
    child: Text('...'),
  );
}
```

## use_is_even_rather_than_modulo

https://dart.dev/tools/linter-rules/use_is_even_rather_than_modulo

数値が奇数か偶数かを調べる場合は`isEven`と`isOdd`が使える。

```dart
  if(2.isEven){
    print("2は偶数です");
  }
  
  if(13.isOdd){
    print("13は奇数です");
  }
```

# おまけ
Linterのルール一覧を読んで、個人的に設定しておいたほうがよさそうなことを記載しました。
よかったら使ってみてください。

```yaml
include: package:flutter_lints/flutter.yaml

linter:
  rules:
    # Error rules
    always_use_package_imports: true
    avoid_print: true
    avoid_relative_lib_imports: true
    no_duplicate_case_values: true

    # Style rules
    always_declare_return_types: true
    always_put_control_body_on_new_line: true
    always_put_required_named_parameters_first: true
    avoid_init_to_null: true
    avoid_multiple_declarations_per_line: true
    avoid_positional_boolean_parameters: true
    avoid_unused_constructor_parameters: true
    camel_case_types: true
    constant_identifier_names: true
    curly_braces_in_flow_control_structures: true
    directives_ordering: true
    empty_catches: true
    empty_constructor_bodies: true
    eol_at_end_of_file: true
    exhaustive_cases: true
    file_names: true
    flutter_style_todos: true
    library_names: true
    library_prefixes: true
    lines_longer_than_80_chars: true
    matching_super_parameters: true
    no_leading_underscores_for_library_prefixes: true
    no_leading_underscores_for_local_identifiers: true
    no_literal_bool_comparisons: true
    non_constant_identifier_names: true
    noop_primitive_operations: true
    only_throw_errors: true
    overridden_fields: true
    parameter_assignments: true
    prefer_asserts_in_initializer_lists: true
    prefer_asserts_with_message: true
    prefer_conditional_assignment: true
    prefer_const_constructors: true
    prefer_const_constructors_in_immutables: true
    prefer_const_declarations: true
    prefer_const_literals_to_create_immutables: true
    prefer_constructors_over_static_methods: true
    prefer_contains: true
    # 好みによって切り替える
    prefer_double_quotes: true
    prefer_expression_function_bodies: true
    prefer_final_fields: true
    prefer_final_in_for_each: true
    prefer_final_locals: true
    # 好みによって切り替える
    # prefer_final_parameters: true
    prefer_for_elements_to_map_fromIterable: true
    prefer_function_declarations_over_variables: true
    prefer_if_null_operators: true
    prefer_initializing_formals: true
    prefer_inlined_adds: true
    prefer_interpolation_to_compose_strings: true
    prefer_is_empty: true
    prefer_is_not_empty: true
    prefer_is_not_operator: true
    prefer_iterable_whereType: true
    prefer_null_aware_method_calls: true
    prefer_spread_collections: true
    prefer_typing_uninitialized_variables: true
    # 好みによって切り替える(trueにすると大変そう)
    public_member_api_docs: true
    recursive_getters: true
    require_trailing_commas: true
    sized_box_for_whitespace: true
    sized_box_shrink_expand: true
    slash_for_doc_comments: true
    sort_child_properties_last: true
    sort_constructors_first: true
    sort_unnamed_constructors_first: true
    type_annotate_public_apis: true
    type_init_formals: true
    type_literal_in_constant_pattern: true
    unawaited_futures: true
    unnecessary_await_in_return: true
    unnecessary_brace_in_string_interps: true
    unnecessary_const: true
    unnecessary_constructor_name: true
    unnecessary_getters_setters: true
    # 好みで切り替える
    #unnecessary_lambdas: true
    unnecessary_late: true
    unnecessary_new: true
    unnecessary_null_aware_assignments: true
    unnecessary_null_in_if_null_operators: true
    unnecessary_nullable_for_final_variable_declarations: true
    unnecessary_overrides: true
    unnecessary_parenthesis: true
    unnecessary_raw_strings: true
    unnecessary_string_escapes: true
    unnecessary_string_interpolations: true
    unnecessary_this: true
    unnecessary_to_list_in_spreads: true
    use_colored_box: true
    use_decorated_box: true
    use_is_even_rather_than_modulo: true
    use_rethrow_when_possible: true
    use_string_buffers: true
    use_test_throws_matchers: true
    
    # Pub rules
    depend_on_referenced_packages: true
    secure_pubspec_urls: true
    sort_pub_dependencies: true
```