Dart
This page curates example ast-grep rules for Dart code.
Why does a Dart call pattern return no matches?
Dart does not allow expressions at the top level. As a result, a standalone pattern such as debugPrint($$$ARGS) can be parsed as a function_signature instead of a call_expression. You can confirm the parsed node with --debug-query:
ast-grep run -p 'debugPrint($$$ARGS)' -l dart --debug-query=patternFor a direct call whose callee is an identifier, provide a valid enclosing function and select the call expression to remove the ambiguity:
ast-grep run \
-p 'void _() { debugPrint($$$ARGS); }' \
--selector call_expression \
-l dartThe equivalent YAML rule uses a pattern object:
id: find-direct-debug-print
language: dart
rule:
pattern:
context: 'void _() { debugPrint($$$ARGS); }'
selector: call_expressionThis example intentionally matches only direct debugPrint(...) calls. For a copy-ready rule that also covers member, null-aware, generic, parenthesized, null-asserted, common single-level arrow-body, and direct cascade calls, see Find Dart Calls by Callee Name. That catalog entry includes an exact CLI command, a live Dart playground, and the rule's tested boundaries.
Find Dart Calls by Callee Name
Description
A standalone Dart pattern such as targetFn($$$ARGS) can be parsed as a function signature instead of a call expression. This rule avoids that ambiguity and finds common call forms whose syntactic callee is the exact identifier targetFn.
It covers direct, member, null-aware, generic, nested, parenthesized, null-asserted, common single-level sync or async arrow-body, and direct cascade calls. It does not match tear-offs, comments, strings, explicit new or const constructor expressions, operators, or indirect invocations whose callee is not syntactically named targetFn.
Copy the YAML into find-dart-calls-by-name.yml, replace every occurrence of targetFn with the simple Dart identifier you need, and run:
ast-grep scan --rule find-dart-calls-by-name.yml lib testYAML
id: find-dart-calls-by-name
message: Call to targetFn
severity: hint
language: dart
rule:
any:
# Ordinary, member, null-aware, generic, nested, parenthesized, and
# null-asserted calls.
- all:
- kind: call_expression
- has:
field: function
stopBy: neighbor
all:
- regex: '(?s)^(?:.*\.)?(?:\s|\(|/\*.*?\*/|//[^\n]*(?:\n|$))*targetFn(?:\s|/\*.*?\*/|//[^\n]*(?:\n|$))*(?:<.*>)?(?:\s|\)|!)*$'
- not:
kind: function_expression
- not:
has:
kind: function_expression
stopBy: end
# tree-sitter-dart represents a direct arrow-body call as a call_expression
# whose function field contains the enclosing function expression.
- all:
- kind: call_expression
- has:
field: function
stopBy: neighbor
all:
- regex: '(?s)^.*=>(?:\s|\(|/\*.*?\*/|//[^\n]*(?:\n|$)|await\b)*(?:.*\.)?targetFn(?:\s|/\*.*?\*/|//[^\n]*(?:\n|$))*(?:<.*>)?(?:\s|\)|!)*$'
- any:
- all:
- kind: function_expression
- not:
has:
kind: function_expression
stopBy: end
- all:
- any:
- kind: member_expression
- kind: null_aware_member_expression
- kind: instantiation_expression
- kind: null_assertion_expression
- has:
kind: function_expression
stopBy: end
# obj..targetFn(...) and obj?..targetFn(...)
- all:
- kind: cascade_call_expression
- has:
field: property
stopBy: neighbor
regex: '^targetFn$'Example
void example(Service service, Service? maybeService) {
targetFn('direct');
service.targetFn('member');
maybeService?.targetFn('null-aware');
service..targetFn('cascade');
targetFn<int>(1);
final callback = () => targetFn('arrow body');
final tearOff = targetFn;
service.targetFn.toString();
const text = 'targetFn("not code")';
}For an arrow-body call, the reported match can include the whole closure because that is the range exposed by the current Dart grammar.
This is a syntax rule, not type resolution. Dart can omit new and const, so Type.targetFn() can be either a static method call or a named constructor and is included. Chained cascade sections such as service..child.targetFn() and nested arrow bodies such as () => () => targetFn() are outside this rule's current scope.