findCanonicalModelElementFor method

ModelElement findCanonicalModelElementFor(
  1. Element e,
  2. {Container preferredClass}
)

Tries to find a canonical ModelElement for this element. If we know this element is related to a particular class, pass preferredClass to disambiguate.

This doesn't know anything about PackageGraph.inheritThrough and probably shouldn't, so using it with Inheritables without special casing is not advised.

Implementation

ModelElement findCanonicalModelElementFor(Element e,
    {Container preferredClass}) {
  assert(allLibrariesAdded);
  var lib = findCanonicalLibraryFor(e);
  if (preferredClass != null && preferredClass is Container) {
    Container canonicalClass =
        findCanonicalModelElementFor(preferredClass.element);
    if (canonicalClass != null) preferredClass = canonicalClass;
  }
  if (lib == null && preferredClass != null) {
    lib = findCanonicalLibraryFor(preferredClass.element);
  }
  ModelElement modelElement;
  // For elements defined in extensions, they are canonical.
  if (e?.enclosingElement is ExtensionElement) {
    lib ??= Library(e.enclosingElement.library, packageGraph);
    // (TODO:keertip) Find a better way to exclude members of extensions
    //  when libraries are specified using the "--include" flag
    if (lib?.isDocumented == true) {
      return ModelElement.from(e, lib, packageGraph);
    }
  }
  // TODO(jcollins-g): Special cases are pretty large here.  Refactor to split
  // out into helpers.
  // TODO(jcollins-g): The data structures should be changed to eliminate guesswork
  // with member elements.
  if (e is ClassMemberElement || e is PropertyAccessorElement) {
    e = e.declaration;
    var candidates = <ModelElement>{};
    var iKey = Tuple2<Element, Library>(e, lib);
    var key =
        Tuple4<Element, Library, Class, ModelElement>(e, lib, null, null);
    var keyWithClass = Tuple4<Element, Library, Class, ModelElement>(
        e, lib, preferredClass, null);
    if (allConstructedModelElements.containsKey(key)) {
      candidates.add(allConstructedModelElements[key]);
    }
    if (allConstructedModelElements.containsKey(keyWithClass)) {
      candidates.add(allConstructedModelElements[keyWithClass]);
    }
    if (candidates.isEmpty && allInheritableElements.containsKey(iKey)) {
      candidates
          .addAll(allInheritableElements[iKey].where((me) => me.isCanonical));
    }
    Class canonicalClass = findCanonicalModelElementFor(e.enclosingElement);
    if (canonicalClass != null) {
      candidates.addAll(canonicalClass.allCanonicalModelElements.where((m) {
        return m.element == e;
      }));
    }
    var matches = <ModelElement>{...candidates.where((me) => me.isCanonical)};

    // It's possible to find accessors but no combos.  Be sure that if we
    // have Accessors, we find their combos too.
    if (matches.any((me) => me is Accessor)) {
      var combos =
          matches.whereType<Accessor>().map((a) => a.enclosingCombo).toList();
      matches.addAll(combos);
      assert(combos.every((c) => c.isCanonical));
    }

    // This is for situations where multiple classes may actually be canonical
    // for an inherited element whose defining Class is not canonical.
    if (matches.length > 1 &&
        preferredClass != null &&
        preferredClass is Class) {
      // Search for matches inside our superchain.
      var superChain = preferredClass.superChain
          .map((et) => et.modelElement)
          .cast<Class>()
          .toList();
      superChain.add(preferredClass);
      matches.removeWhere((me) =>
          !superChain.contains((me as EnclosedElement).enclosingElement));
      // Assumed all matches are EnclosedElement because we've been told about a
      // preferredClass.
      var enclosingElements = {
        ...matches
            .map((me) => (me as EnclosedElement).enclosingElement as Class)
      };
      for (var c in superChain.reversed) {
        if (enclosingElements.contains(c)) {
          matches.removeWhere(
              (me) => (me as EnclosedElement).enclosingElement != c);
        }
        if (matches.length <= 1) break;
      }
    }

    // Prefer a GetterSetterCombo to Accessors.
    if (matches.any((me) => me is GetterSetterCombo)) {
      matches.removeWhere((me) => me is Accessor);
    }

    assert(matches.length <= 1);
    if (matches.isNotEmpty) {
      modelElement = matches.first;
    }
  } else {
    if (lib != null) {
      if (e is PropertyInducingElement) {
        var getter = e.getter != null
            ? ModelElement.from(e.getter, lib, packageGraph)
            : null;
        var setter = e.setter != null
            ? ModelElement.from(e.setter, lib, packageGraph)
            : null;
        modelElement = ModelElement.fromPropertyInducingElement(
            e, lib, packageGraph,
            getter: getter, setter: setter);
      } else {
        modelElement = ModelElement.from(e, lib, packageGraph);
      }
    }
    assert(modelElement is! Inheritable);
    if (modelElement != null && !modelElement.isCanonical) {
      modelElement = null;
    }
  }
  // Prefer Fields.
  if (e is PropertyAccessorElement && modelElement is Accessor) {
    modelElement = (modelElement as Accessor).enclosingCombo;
  }
  return modelElement;
}