はじめに
こんにちは!そたです。
今回は Dart 3.10 で新しく追加された Analyzer plugins という機能を使って、オリジナルのlintルールを作ってみました!
え?custom lintって前からなかった?
以前から custom_lint というカスタムのlintルールを作れるパッケージはありましたが、これはサードパーティーによるパッケージです。
Analyzer pluginsはDart公式が提供する ファーストパーティ のプラグインシステムになります。
色々と違いはあるのですが、一番大きいのは従来dart analyzeとは別のコマンドを実行する必要があったのが、Analyzer Pluginsを利用すれば標準の dart analyze コマンドで自作ルールが動かせることだと思います!
これによりCIの設定もシンプルになりますね。
ということで早速簡単なlintルールを作ってみましょう。
公式のドキュメントがあるので、基本的にはこれに沿って進めていけば大丈夫です。
今回作るもの
デザインシステムを導入しているプロジェクトあるあるだと思いますが、「FlutterのText Widgetを直接使わないで!カスタムテキストコンポーネント使って!」ってルールがあったりしますよね。
今回はそれを自動で検出する Text Widget禁止ルール を作ってみます。
環境
- Dart SDK: 3.10.0 以上
- Flutter: 3.38.0 以上
プロジェクト構成
最終的にこんな感じの構成になります:
analyzer_sample/
├── lib/
│ ├── main.dart # プラグインのエントリーポイント
│ └── rules/
│ └── ban_text_rule.dart # Text禁止ルールの実装
├── example/ # 動作確認用Flutterアプリ
├── pubspec.yaml
└── analysis_options.yaml
Step 1: pubspec.yaml の設定
まずは必要なパッケージを追加します。
name: analyzer_sample
description: A custom analyzer plugin to ban specific widgets like Text.
version: 1.0.0
environment:
sdk: ^3.10.0
dependencies:
# これが必要!
analysis_server_plugin: ^0.3.0
analyzer_plugin: ^0.13.0
analyzer: ^8.0.0
Step 2: エントリーポイントの作成
lib/main.dart を作成します。
import 'package:analysis_server_plugin/plugin.dart';
import 'package:analysis_server_plugin/registry.dart';
import 'rules/ban_text_rule.dart';
// この変数名は "plugin" である必要がある
final plugin = AnalyzerSamplePlugin();
class AnalyzerSamplePlugin extends Plugin {
@override
String get name => 'analyzer_sample';
@override
void register(PluginRegistry registry) {
// ここでルールを登録
registry.registerLintRule(BanTextRule());
}
}
plugin という名前のトップレベル変数を定義する必要があります。Analysis Serverがこの変数を探しに来ます。
Step 3: ルールの実装(ここが本題!)
lib/rules/ban_text_rule.dart を作成します。
import 'package:analyzer/analysis_rule/analysis_rule.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
// エラーコードの定義
const _banTextCode = LintCode(
'ban_text_widget',
'Usage of Text widget is prohibited.',
correctionMessage:
'Use a custom text widget from your design system instead.',
);
// ルールクラス
class BanTextRule extends AnalysisRule {
BanTextRule()
: super(
name: 'ban_text_widget',
description:
'Text widget is prohibited. Use a custom text widget instead.',
);
@override
LintCode get diagnosticCode => _banTextCode;
@override
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
// 「インスタンス生成」のノードを監視
registry.addInstanceCreationExpression(this, _BanTextVisitor(this));
}
}
// AST(抽象構文木)を巡回するVisitor
class _BanTextVisitor extends SimpleAstVisitor<void> {
final BanTextRule rule;
_BanTextVisitor(this.rule);
@override
void visitInstanceCreationExpression(InstanceCreationExpression node) {
final type = node.staticType;
if (type == null) return;
// ここで利用できるAstはResolved ASTなのでelementへのアクセスが可能
final element = type.element;
// クラス名が 'Text' かチェック
if (element is ClassElement && element.name == 'Text') {
// Flutter SDK の Text かチェック(自作クラスとの区別)
final libraryUri = element.library.identifier;
if (libraryUri.startsWith('package:flutter/')) {
// 警告を報告
rule.reportAtNode(node);
}
}
}
}
ASTとは??
先日FlutterKaigi 2025でASTについての解説をしてきたのでぜひご覧ください!
資料はこちらです
この記事でも軽く説明しておきます。
AST(抽象構文木)とは
ソースコードの構文の構造を木構造で表現したものです。これを利用するとコードを文字列リテラルとして扱うのに対し格段に解析しやすくなります。
Visitorとは?
構築されたASTノードをたどり、解析するためのデザインパターンです。
これを用いることで目的の属性を持つASTノードのみを簡単に解析できると捉えておいてください。
今回のコードの例では、ソースコードの中で「クラスのインスタンス化を行なっているASTノード」を探し、それがflutterパッケージのTextWidgetかどうかを判定しています。
Step 4: analysis_options.yaml で有効化
include: package:flutter_lints/flutter.yaml
plugins:
analyzer_sample:
path: .
diagnostics:
ban_text_widget: true # ルールを有効化
diagnostics でルールを明示的に true にしないと動きません。lintルールはデフォルトで無効になっています。
動かしてみよう!
試しにFlutterのカウンターアプリで確認してみます。
$ flutter analyze
Analyzing example...
info • Usage of Text widget is prohibited • lib/main.dart:86:16 • ban_text_widget
info • Usage of Text widget is prohibited • lib/main.dart:107:13 • ban_text_widget
info • Usage of Text widget is prohibited • lib/main.dart:108:13 • ban_text_widget
3 issues found.
ちゃんとText Widgetの使用箇所が検出されました!
警告を抑制したい場合
特定の箇所でどうしてもTextを使いたい場合は、gnore コメントが使えるのですが、パッケージ名も指定しないといけないことに注意してください
// ignore: analyzer_sample/ban_text_widget
Text('This is allowed');
サンプルコード
今回作成したプラグインは以下のリポジトリで公開しています。
cloneしてすぐに動作確認できるので、ぜひ試してみてください!
👉 https://github.com/SoutaTanaka/analyzer_sample
git clone https://github.com/SoutaTanaka/analyzer_sample.git
cd analyzer_sample
dart pub get
dart analyze