Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the fully-qualified target of a MethodInvocation?

I'm trying to find usages of a particular method. In this case, I want to find usages/invocations of foo.go(), but NOT bar.go(). The below code will find all invocations of go() on ANY class. In the code below, node.target gives me simply x which is the var name, but I'm struggling to figure out which class the method belongs to.

test.dart

void main() {
  var x = new Foo();
  x.go();

  x = new Bar();
  x.go();
}

class Foo {
  go() {
    print('I am foo');
  }
}

class Bar {
  go() {
    print('I am bar');
  }
}

analyze.dart

import 'package:analyzer/analyzer.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';

void main() {
  var targetFile = parseDartFile('test.dart');
  var visitor    = new UsageVisitor('foo.go');
  targetFile.visitChildren(visitor);
}

class UsageVisitor extends UnifyingAstVisitor {
  String _class;
  String _method;

  UsageVisitor(this._class, this._method);

  @override
  visitMethodInvocation(MethodInvocation node) {
    print({
      'target'    : node.target.toString(),    // in both cases, gives "x" -- I need "Foo" and "Bar", respectively
      'methodName': node.methodName.toString() // gives "go"
    });

    return super.visitNode(node);
  }
}

How do I tell the difference (at the analyzer level), between foo.go() and bar.go() ?

like image 637
KOGI Avatar asked Jan 16 '17 21:01

KOGI


2 Answers

The problem is that test.dart is being parsed without context needed to resolve the elements contained within.

An example of this is included in the analyzer package within the Dart SDK

Breaking that down to its steps you have:

1 - Setup resolvers

PhysicalResourceProvider resourceProvider = PhysicalResourceProvider.INSTANCE;
DartSdk sdk = new FolderBasedDartSdk(resourceProvider, resourceProvider.getFolder(args[0]));

var resolvers = [
  new DartUriResolver(sdk),
  new ResourceUriResolver(resourceProvider)
];

2 - Create an AnalysisContext using the resolvers

AnalysisContext context = AnalysisEngine.instance.createAnalysisContext()
  ..sourceFactory = new SourceFactory(resolvers);

3 - Add your Source to a ChangeSet

Source source = new FileSource(resourceProvider.getFile(args[1]));
ChangeSet changeSet = new ChangeSet()..addedSource(source);

3.1 - Apply the ChangeSet to the AnalysisContext

context.applyChanges(changeSet);

3.2 - Get the LibraryElement

I'm not certain if this is always needed. Maybe it can be skipped for simple files. ¯(◉◡◔)/¯

LibraryElement libElement = context.computeLibraryElement(source);

4 - Parse your Source!

CompilationUnit resolvedUnit = context.resolveCompilationUnit(source, libElement);

5 - Run your Visitor

resolvedUnit.accept(visitor)

Unfortunately that's a lot more code than just calling parseDartFile() but the good news is that your Visitor should work without further changes.

Good luck!

like image 133
Jon Kirkman Avatar answered Oct 19 '22 09:10

Jon Kirkman


I found something, but I am not used to the analyzer, maybe it can be simplify and it works only for you case.

@override
visitMethodInvocation(MethodInvocation node) {
  if (node.methodName.toString() == "go" && node.target.toString() == "x") {
    for (dynamic child in node.parent?.parent?.childEntities) {
      if (child is VariableDeclarationStatement) {
        if ((child.childEntities.first as VariableDeclarationList).type.toString() == "Foo" &&
          ((child.childEntities.first as VariableDeclarationList).childEntities.elementAt(1) as VariableDeclaration)
                  .beginToken
                  .toString() ==
              "x") {
          print(node);
        }
      }
    }
  }
  return super.visitNode(node);
}
like image 31
Hadrien Lejard Avatar answered Oct 19 '22 10:10

Hadrien Lejard