Skip to content

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:

sh
ast-grep scan --rule find-dart-calls-by-name.yml lib test

YAML

yaml
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

dart
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.

Contributed by

Vijay Muthukumaran

Made with ❤️ with Rust