runWithReporter method
Override this method to implement your lint rule.
Use context to register callbacks for AST node types:
context.addMethodInvocation((node) {
if (condition) {
reporter.atNode(node);
}
});
Implementation
@override
void runWithReporter(
SaropaDiagnosticReporter reporter,
SaropaContext context,
) {
context.addMethodInvocation((MethodInvocation node) {
// Identify a real dialog launch, not any identifier containing "Dialog".
// A bare `name.contains('Dialog')` flags l10n string getters such as
// `emergencyDirectoryDialogHeader(...)` (returns String) as dialog
// launches, demanding a meaningless pumpAndSettle. A genuine dialog API
// is either a known launcher OR a "*Dialog*" call that returns an
// awaitable (Future/void) — a String/int/bool getter never shows a dialog.
final String invokedName = node.methodName.name;
const Set<String> dialogLaunchers = <String>{
'showDialog',
'showGeneralDialog',
'showAdaptiveDialog',
'showDialogCommon',
'showModalBottomSheet',
'showCupertinoDialog',
'showCupertinoModalPopup',
};
if (!dialogLaunchers.contains(invokedName)) {
if (!invokedName.contains('Dialog')) return;
// A "*Dialog*" call is a real dialog launch only when its return type is
// a CONFIRMED awaitable (Future/void). A String/int/bool getter such as
// the l10n `emergencyDirectoryDialogHeader(...)` is not. Unresolved
// (null) type is NOT treated as awaitable: genuine launchers are already
// matched by name above, so the type-fallback path only needs to admit
// confirmed-awaitable custom helpers, never guess on unresolved types.
final DartType? returnType = node.staticType;
final bool awaitable =
returnType != null &&
(returnType.isDartAsyncFuture || returnType is VoidType);
if (!awaitable) return;
}
// Check if inside a test function
if (!_isInsideTestForDialog(node)) return;
// Look for pumpAndSettle after the dialog
final AstNode? block = _findContainingBlockForDialog(node);
if (block == null) return;
final String blockSource = block.toSource();
final int dialogIndex = blockSource.indexOf(node.toSource());
// Check if pumpAndSettle comes after showDialog
final String afterDialog = blockSource.substring(dialogIndex);
if (!afterDialog.contains('pumpAndSettle')) {
reporter.atNode(node);
}
});
}