blob: 63c27d618c981a3e55aa8c5a822cfcda88b952c3 [file]
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/resolver/type_analyzer.dart';
class CommentReferenceResolver {
final TypeAnalyzer _typeAnalyzer;
CommentReferenceResolver(this._typeAnalyzer);
/// Resolves [commentReference].
void resolve(CommentReferenceImpl commentReference) {
_typeAnalyzer.diagnosticReporter.lockLevel++;
try {
var components = commentReference.components;
if (components.length == 1) {
components.single.element = _resolveUnqualified(components.single);
} else if (components.length == 2) {
_resolveTwoComponents(components[0], components[1]);
} else if (components.length == 3) {
_resolveThreeComponents(components[0], components[1], components[2]);
}
} finally {
_typeAnalyzer.diagnosticReporter.lockLevel--;
}
}
/// Resolves [name] as a member of [container]: declared members first, then
/// inherited ones. Constructors are members only for qualified references,
/// because a bare name never refers to a constructor.
Element? _resolveInstanceMember(
InstanceElement container,
String name, {
required bool allowConstructors,
}) {
var declaredElement =
container.getMethod(name) ??
container.getGetter(name) ??
container.getSetter(name);
if (declaredElement != null) {
return declaredElement;
}
if (container is InterfaceElement) {
if (allowConstructors) {
var constructor = container.getNamedConstructor(name);
if (constructor != null) {
return constructor;
}
}
var memberName = Name(container.library.uri, name);
return _typeAnalyzer.inheritance.getMember(container, memberName) ??
_typeAnalyzer.inheritance.getMember(container, memberName.forSetter);
}
return null;
}
void _resolveThreeComponents(
CommentReferenceComponentImpl first,
CommentReferenceComponentImpl second,
CommentReferenceComponentImpl third,
) {
var firstElement = _resolveUnqualified(first);
first.element = firstElement;
// The only valid firstElement is a PrefixElement.
if (firstElement is! PrefixElement) {
return;
}
var prefixScope = firstElement.scope;
var secondLookupResult = prefixScope.lookup(second.name.lexeme);
var secondElement = secondLookupResult.getter ?? secondLookupResult.setter;
second.element = secondElement;
// When resolving `third`, use the aliased element.
if (secondElement is TypeAliasElement) {
secondElement = secondElement.aliasedType.element;
}
if (secondElement is InstanceElement) {
third.element = _resolveInstanceMember(
secondElement,
third.name.lexeme,
allowConstructors: true,
);
}
}
void _resolveTwoComponents(
CommentReferenceComponentImpl first,
CommentReferenceComponentImpl second,
) {
var firstElement = _resolveUnqualified(first);
first.element = firstElement;
if (firstElement == null) {
return;
}
// When resolving `second`, use the aliased element.
if (firstElement is TypeAliasElement) {
firstElement = firstElement.aliasedType.element;
}
switch (firstElement) {
case PrefixElement():
var prefixScope = firstElement.scope;
var lookupResult = prefixScope.lookup(second.name.lexeme);
var element = lookupResult.getter ?? lookupResult.setter;
second.element = element;
case InstanceElement():
second.element = _resolveInstanceMember(
firstElement,
second.name.lexeme,
allowConstructors: true,
);
}
}
/// Resolves the unqualified [component] if possible.
///
/// Returns the resolved element, or `null` if the component could not be
/// resolved. This does not record the results of the resolution.
Element? _resolveUnqualified(CommentReferenceComponentImpl component) {
var scopeLookupResult = component.scopeLookupResult!;
var scopeElement = scopeLookupResult.getter ?? scopeLookupResult.setter;
// Import usage is tracked by lookups in the prefix scope, which `[math]`
// alone does not do; without this, the import would be reported unused.
if (scopeElement is PrefixElementImpl) {
scopeElement.scope.notifyPrefixUsedWithoutName();
}
if (scopeElement != null) {
return scopeElement;
}
var enclosingInstanceElement = _typeAnalyzer.enclosingInstanceElement;
if (enclosingInstanceElement == null) {
return null;
}
var name = component.name.lexeme;
var receiverType = _typeAnalyzer.typeSystem.resolveToBound(
enclosingInstanceElement.thisType,
);
// The instance scope yields nothing for instance members, so look them up.
if (receiverType is InterfaceTypeImpl) {
var member = _resolveInstanceMember(
receiverType.element,
name,
allowConstructors: false,
);
if (member != null) {
return member;
}
}
var extensionResult = _typeAnalyzer.extensionResolver.findExtension(
receiverType,
component,
Name(_typeAnalyzer.definingLibrary.uri, name),
);
return extensionResult.getter2 ?? extensionResult.setter2;
}
}