/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.services.completion;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.dart.engine.AnalysisEngine;
import com.google.dart.engine.ast.Annotation;
import com.google.dart.engine.ast.ArgumentList;
import com.google.dart.engine.ast.AsExpression;
import com.google.dart.engine.ast.AssertStatement;
import com.google.dart.engine.ast.AssignmentExpression;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.BinaryExpression;
import com.google.dart.engine.ast.Block;
import com.google.dart.engine.ast.BooleanLiteral;
import com.google.dart.engine.ast.BreakStatement;
import com.google.dart.engine.ast.CatchClause;
import com.google.dart.engine.ast.ClassDeclaration;
import com.google.dart.engine.ast.ClassTypeAlias;
import com.google.dart.engine.ast.Combinator;
import com.google.dart.engine.ast.Comment;
import com.google.dart.engine.ast.CommentReference;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.ConstructorDeclaration;
import com.google.dart.engine.ast.ConstructorFieldInitializer;
import com.google.dart.engine.ast.ConstructorName;
import com.google.dart.engine.ast.ContinueStatement;
import com.google.dart.engine.ast.Declaration;
import com.google.dart.engine.ast.Directive;
import com.google.dart.engine.ast.DoStatement;
import com.google.dart.engine.ast.DoubleLiteral;
import com.google.dart.engine.ast.ExportDirective;
import com.google.dart.engine.ast.Expression;
import com.google.dart.engine.ast.ExpressionFunctionBody;
import com.google.dart.engine.ast.ExpressionStatement;
import com.google.dart.engine.ast.ExtendsClause;
import com.google.dart.engine.ast.FieldFormalParameter;
import com.google.dart.engine.ast.ForEachStatement;
import com.google.dart.engine.ast.ForStatement;
import com.google.dart.engine.ast.FormalParameter;
import com.google.dart.engine.ast.FormalParameterList;
import com.google.dart.engine.ast.FunctionDeclaration;
import com.google.dart.engine.ast.FunctionTypeAlias;
import com.google.dart.engine.ast.Identifier;
import com.google.dart.engine.ast.IfStatement;
import com.google.dart.engine.ast.ImplementsClause;
import com.google.dart.engine.ast.ImportDirective;
import com.google.dart.engine.ast.InstanceCreationExpression;
import com.google.dart.engine.ast.InterpolationExpression;
import com.google.dart.engine.ast.IsExpression;
import com.google.dart.engine.ast.LibraryIdentifier;
import com.google.dart.engine.ast.MethodDeclaration;
import com.google.dart.engine.ast.MethodInvocation;
import com.google.dart.engine.ast.NamedExpression;
import com.google.dart.engine.ast.NamespaceDirective;
import com.google.dart.engine.ast.NodeList;
import com.google.dart.engine.ast.ParenthesizedExpression;
import com.google.dart.engine.ast.PartOfDirective;
import com.google.dart.engine.ast.PrefixedIdentifier;
import com.google.dart.engine.ast.PropertyAccess;
import com.google.dart.engine.ast.RedirectingConstructorInvocation;
import com.google.dart.engine.ast.ReturnStatement;
import com.google.dart.engine.ast.SimpleFormalParameter;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.SimpleStringLiteral;
import com.google.dart.engine.ast.Statement;
import com.google.dart.engine.ast.StringLiteral;
import com.google.dart.engine.ast.SuperConstructorInvocation;
import com.google.dart.engine.ast.SuperExpression;
import com.google.dart.engine.ast.SwitchCase;
import com.google.dart.engine.ast.SwitchMember;
import com.google.dart.engine.ast.SwitchStatement;
import com.google.dart.engine.ast.ThisExpression;
import com.google.dart.engine.ast.TryStatement;
import com.google.dart.engine.ast.TypeArgumentList;
import com.google.dart.engine.ast.TypeName;
import com.google.dart.engine.ast.TypeParameter;
import com.google.dart.engine.ast.TypeParameterList;
import com.google.dart.engine.ast.VariableDeclaration;
import com.google.dart.engine.ast.VariableDeclarationList;
import com.google.dart.engine.ast.WhileStatement;
import com.google.dart.engine.ast.WithClause;
import com.google.dart.engine.ast.visitor.GeneralizingAstVisitor;
import com.google.dart.engine.context.AnalysisContext;
import com.google.dart.engine.context.AnalysisException;
import com.google.dart.engine.element.ClassElement;
import com.google.dart.engine.element.CompilationUnitElement;
import com.google.dart.engine.element.ConstructorElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ElementKind;
import com.google.dart.engine.element.ExecutableElement;
import com.google.dart.engine.element.FieldElement;
import com.google.dart.engine.element.FunctionElement;
import com.google.dart.engine.element.FunctionTypeAliasElement;
import com.google.dart.engine.element.ImportElement;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.MethodElement;
import com.google.dart.engine.element.ParameterElement;
import com.google.dart.engine.element.PrefixElement;
import com.google.dart.engine.element.PropertyAccessorElement;
import com.google.dart.engine.element.PropertyInducingElement;
import com.google.dart.engine.element.TopLevelVariableElement;
import com.google.dart.engine.element.TypeParameterElement;
import com.google.dart.engine.element.VariableElement;
import com.google.dart.engine.error.AnalysisError;
import com.google.dart.engine.internal.context.InternalAnalysisContext;
import com.google.dart.engine.internal.resolver.TypeProvider;
import com.google.dart.engine.internal.type.DynamicTypeImpl;
import com.google.dart.engine.scanner.Token;
import com.google.dart.engine.scanner.TokenType;
import com.google.dart.engine.sdk.DartSdk;
import com.google.dart.engine.sdk.SdkLibrary;
import com.google.dart.engine.search.SearchEngine;
import com.google.dart.engine.search.SearchFilter;
import com.google.dart.engine.search.SearchMatch;
import com.google.dart.engine.search.SearchScope;
import com.google.dart.engine.search.SearchScopeFactory;
import com.google.dart.engine.services.assist.AssistContext;
import com.google.dart.engine.services.internal.correction.CorrectionUtils;
import com.google.dart.engine.source.FileBasedSource;
import com.google.dart.engine.source.Source;
import com.google.dart.engine.type.FunctionType;
import com.google.dart.engine.type.InterfaceType;
import com.google.dart.engine.type.Type;
import com.google.dart.engine.type.UnionType;
import com.google.dart.engine.utilities.ast.ScopedNameFinder;
import com.google.dart.engine.utilities.dart.ParameterKind;
import com.google.dart.engine.utilities.translation.DartBlockBody;
import com.google.dart.engine.utilities.translation.DartOmit;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The analysis engine for code completion.
* <p>
* Note: During development package-private methods are used to group element-specific completion
* utilities.
* <p>
* TODO: Recognize when completion is requested in the middle of a multi-character operator.
* Re-write the AST as it would be if an identifier were present at the completion point then
* restart the analysis.
*
* @coverage com.google.dart.engine.services.completion
*/
public class CompletionEngine {
abstract class AstNodeClassifier extends GeneralizingAstVisitor<Void> {
@Override
public Void visitNode(AstNode node) {
return null;
}
}
class CommentReferenceCompleter extends AstNodeClassifier {
private final SimpleIdentifier identifier;
private final NameCollector names;
private final Set<Element> enclosingElements = Sets.newHashSet();
public CommentReferenceCompleter(SimpleIdentifier identifier) {
this.identifier = identifier;
filter = createFilter(identifier);
names = collectTopLevelElementVisibleAt(identifier);
}
@Override
public Void visitClassDeclaration(ClassDeclaration node) {
ClassElement classElement = node.getElement();
names.addNamesDefinedByHierarchy(classElement, false);
enclosingElements.add(classElement);
return null;
}
@Override
public Void visitComment(Comment node) {
node.getParent().accept(this);
// propose names
for (Element element : names.getUniqueElements()) {
CompletionProposal proposal = createProposal(element, identifier);
if (proposal != null) {
// we don't want to add arguments, just names
if (element instanceof MethodElement || element instanceof FunctionElement) {
proposal.setKind(ProposalKind.METHOD_NAME);
}
// elevate priority for local elements
if (enclosingElements.contains(element.getEnclosingElement())) {
proposal.setRelevance(CompletionProposal.RELEVANCE_HIGH);
}
// propose
requestor.accept(proposal);
}
}
// done
return null;
}
@Override
public Void visitConstructorDeclaration(ConstructorDeclaration node) {
visitExecutableDeclaration(node);
// pass through
return node.getParent().accept(this);
}
@Override
public Void visitFunctionDeclaration(FunctionDeclaration node) {
visitExecutableDeclaration(node);
return null;
}
@Override
public Void visitFunctionTypeAlias(FunctionTypeAlias node) {
FunctionTypeAliasElement element = node.getElement();
names.mergeNames(element.getParameters());
enclosingElements.add(element);
return null;
}
@Override
public Void visitMethodDeclaration(MethodDeclaration node) {
visitExecutableDeclaration(node);
// pass through
return node.getParent().accept(this);
}
private void visitExecutableDeclaration(Declaration node) {
ExecutableElement element = (ExecutableElement) node.getElement();
names.mergeNames(element.getParameters());
enclosingElements.add(element);
}
}
// class ContainmentFilter implements SearchFilter {
// ExecutableElement containingElement;
//
// ContainmentFilter(ExecutableElement element) {
// containingElement = element;
// }
//
// @Override
// public boolean passes(SearchMatch match) {
// Element baseElement = match.getElement();
// if (containingElement == null) {
// return baseElement.getEnclosingElement() instanceof CompilationUnitElement;
// }
// return true;
// }
// }
class NameCollector {
private Map<String, List<Element>> uniqueNames = new HashMap<String, List<Element>>();
private Set<Element> potentialMatches;
public NameCollector() {
}
public void addAll(Collection<SimpleIdentifier> values) {
for (SimpleIdentifier id : values) {
mergeName(id.getBestElement());
}
}
public void addLocalNames(SimpleIdentifier identifier) {
AstNode node = identifier;
Declaration decl;
while ((decl = node.getAncestor(Declaration.class)) != null) {
Element declElement = decl.getElement();
if (declElement instanceof ExecutableElement) {
addNamesDefinedByExecutable((ExecutableElement) declElement);
} else {
return;
}
node = decl.getParent();
}
}
void addNamesDefinedByExecutable(ExecutableElement execElement) {
mergeNames(execElement.getParameters());
mergeNames(execElement.getLocalVariables());
mergeNames(execElement.getFunctions());
}
void addNamesDefinedByHierarchy(ClassElement classElement, boolean forSuper) {
addNamesDefinedByTypeHierarchy(classElement.getType(), forSuper);
}
void addNamesDefinedByType(Type type) {
mergeNames(namesDefinedByType(type));
}
void addNamesDefinedByTypeHierarchy(Type type, boolean forSuper) {
mergeNames(namesDefinedByTypeHierarchy(type, forSuper));
// Collect names defined by subtypes separately so they can be identified later.
NameCollector potentialMatchCollector = createNameCollector();
potentialMatchCollector.mergeNames(potentialNamesDefinedByTypeHierarchy(type));
potentialMatches = new HashSet<Element>(potentialMatchCollector.uniqueNames.size());
for (List<Element> matches : potentialMatchCollector.uniqueNames.values()) {
for (Element match : matches) {
mergeName(match);
potentialMatches.add(match);
}
}
}
void addTopLevelNames(ImportElement[] imports, TopLevelNamesKind topKind) {
for (ImportElement imp : imports) {
Collection<Element> elementsCollection = CorrectionUtils.getImportNamespace(imp).values();
List<Element> elements = Lists.newArrayList(elementsCollection);
addTopLevelNames(elements);
}
}
void addTopLevelNames(LibraryElement library, TopLevelNamesKind topKind) {
List<Element> elements = findTopLevelElements(library, topKind);
addTopLevelNames(elements);
}
void addTopLevelNames(LibraryElement[] libraries, TopLevelNamesKind topKind) {
for (LibraryElement library : libraries) {
addTopLevelNames(library, topKind);
}
}
Collection<List<Element>> getNames() {
return uniqueNames.values();
}
Collection<Element> getUniqueElements() {
List<Element> uniqueElements = Lists.newArrayList();
for (List<Element> uniques : uniqueNames.values()) {
Element element = uniques.get(0);
uniqueElements.add(element);
}
return uniqueElements;
}
boolean isPotentialMatch(Element element) {
return potentialMatches != null && potentialMatches.contains(element);
}
void remove(Element element) {
String name = element.getDisplayName();
List<Element> list = uniqueNames.get(name);
if (list == null) {
return;
}
list.remove(element);
if (list.isEmpty()) {
uniqueNames.remove(name);
}
}
private void addTopLevelNames(List<Element> elements) {
mergeNames(findAllTypes(elements));
if (!state.areClassesRequired) {
mergeNames(findAllNotTypes(elements));
mergeNames(findAllPrefixes());
}
}
private Set<Element> filterStaticRefs(ExecutableElement[] elements) {
Set<Element> filteredElements = new HashSet<Element>();
for (ExecutableElement execElem : elements) {
if (!(state.areInstanceReferencesProhibited && !execElem.isStatic()
|| state.areStaticReferencesProhibited && execElem.isStatic()
|| !state.areOperatorsAllowed && execElem.isOperator() || state.areMethodsProhibited
&& !execElem.isOperator())) {
filteredElements.add(execElem);
}
}
return filteredElements;
}
private boolean inPrivateLibrary(InterfaceType type) {
LibraryElement lib = type.getElement().getLibrary();
if (!lib.getName().startsWith("_")) {
return false;
}
// allow completion in the same library
if (lib == getCurrentLibrary()) {
return false;
}
// eliminate types defined in private libraries
return true;
}
/**
* Return the set of elements whose names occur in sets in {@code elementSets}.
*
* @param elementSets
* @return
*/
private Set<Element> intersection(Collection<Set<Element>> elementSets) {
if (elementSets.isEmpty()) {
return Collections.emptySet();
}
Iterator<Set<Element>> i = elementSets.iterator();
Set<String> commonNames = namesOfElements(i.next());
// Compute names common to all element collections.
while (i.hasNext()) {
// Intersection.
commonNames.retainAll(namesOfElements(i.next()));
}
// Compute elements with common names.
Set<Element> elements = new HashSet<Element>();
for (Collection<Element> es : elementSets) {
for (Element e : es) {
if (commonNames.contains(e.getName())) {
elements.add(e);
}
}
}
return elements;
}
private void mergeName(Element element) {
if (element == null) {
return;
}
// ignore private
String name = element.getDisplayName();
if (Identifier.isPrivateName(name)) {
if (!isInCurrentLibrary(element)) {
return;
}
}
// add to other Element(s) with such name
List<Element> dups = uniqueNames.get(name);
if (dups == null) {
dups = new ArrayList<Element>();
uniqueNames.put(name, dups);
}
dups.add(element);
}
private void mergeNames(Collection<Element> elements) {
for (Element element : elements) {
mergeName(element);
}
}
private void mergeNames(Element[] elements) {
mergeNames(Arrays.asList(elements));
}
private Set<Element> namesDefinedByInterfaceType(InterfaceType type) {
if (inPrivateLibrary(type)) {
return Collections.emptySet();
}
Set<Element> elements = new HashSet<Element>();
PropertyAccessorElement[] accessors = type.getAccessors();
elements.addAll(filterStaticRefs(accessors));
MethodElement[] methods = type.getMethods();
elements.addAll(filterStaticRefs(methods));
elements.addAll(Arrays.asList(type.getElement().getTypeParameters()));
return elements;
}
private Set<Element> namesDefinedByInterfaceTypeHierarchy(InterfaceType type, boolean forSuper) {
InterfaceType[] superTypes = type.getElement().getAllSupertypes();
if (!forSuper) {
superTypes = ArrayUtils.add(superTypes, 0, type);
}
return namesDefinedByInterfaceTypes(superTypes);
}
private Set<Element> namesDefinedByInterfaceTypes(InterfaceType[] types) {
Set<Element> elements = new HashSet<Element>();
for (InterfaceType type : types) {
elements.addAll(namesDefinedByType(type));
}
return elements;
}
// The functions [namesDefinedByType], [namesDefinedByTypeHierarchy],
// and [potentialNamesDefinedByTypeHierarchy] all have the same
// structure, but they are not easy to combine in Java w/o higher
// order functions ([union], [intersection], [*namesDefinedByInterfaceType*]).
private Set<Element> namesDefinedByType(Type type) {
if (type instanceof InterfaceType) {
return namesDefinedByInterfaceType((InterfaceType) type);
} else if (type instanceof UnionType) {
List<Set<Element>> nameSets = new ArrayList<Set<Element>>();
for (Type t : ((UnionType) type).getElements()) {
nameSets.add(namesDefinedByType(t));
}
// For strict union types a field/method is only defined on the
// union if it's defined on *all* member types. For non-strict union types
// a field/method is defined if it's defined on *any* member type.
if (AnalysisEngine.getInstance().getStrictUnionTypes()) {
// TODO(collinsn): fix the case where multiple members define the same
// name with different types.
return intersection(nameSets);
} else {
// TODO(collinsn): this doesn't quite do the right thing, since
// other code in this class uniquifies this list by name. See usage
// of the [uniqueNames] field.
return union(nameSets);
}
} else {
// Or should we just raise an exception?
AnalysisEngine.getInstance().getLogger().logError(
"Unexpected type in [NameCollector.namesDefinedByType]: " + type);
return Collections.emptySet();
}
}
// See [namesDefinedByType].
private Set<Element> namesDefinedByTypeHierarchy(Type type, boolean forSuper) {
if (type instanceof InterfaceType) {
return namesDefinedByInterfaceTypeHierarchy((InterfaceType) type, forSuper);
} else if (type instanceof UnionType) {
List<Set<Element>> nameSets = new ArrayList<Set<Element>>();
for (Type t : ((UnionType) type).getElements()) {
nameSets.add(namesDefinedByTypeHierarchy(t, forSuper));
}
if (AnalysisEngine.getInstance().getStrictUnionTypes()) {
return intersection(nameSets);
} else {
return union(nameSets);
}
} else {
AnalysisEngine.getInstance().getLogger().logError(
"Unexpected type in [NameCollector.namesDefinedByTypeHierarchy]: " + type);
return Collections.emptySet();
}
}
private Set<String> namesOfElements(Collection<Element> elements) {
Set<String> names = new HashSet<String>();
for (Element e : elements) {
names.add(e.getName());
}
return names;
}
private Set<Element> potentialNamesDefinedByInterfaceTypeHierarchy(InterfaceType type) {
if (!type.isObject()) {
return namesDefinedByInterfaceTypes(allSubtypes(type.getElement()));
} else {
return Collections.emptySet();
}
}
// See [namesDefinedByType].
//
// The use of [union] and [intersection] here is dual to the usage in
// the related methods. The point is that marking a method as potential
// is more conservative, so e.g. we mark a method as potential on a
// union type if it's potential on *any* member type.
private Set<Element> potentialNamesDefinedByTypeHierarchy(Type type) {
if (type instanceof InterfaceType) {
return potentialNamesDefinedByInterfaceTypeHierarchy((InterfaceType) type);
} else if (type instanceof UnionType) {
List<Set<Element>> nameSets = new ArrayList<Set<Element>>();
for (Type t : ((UnionType) type).getElements()) {
nameSets.add(potentialNamesDefinedByTypeHierarchy(t));
}
if (AnalysisEngine.getInstance().getStrictUnionTypes()) {
return union(nameSets);
} else {
return intersection(nameSets);
}
} else {
AnalysisEngine.getInstance().getLogger().logError(
"Unexpected type in [NameCollector.potentialNamesDefinedByTypeHierarchy]: " + type);
return Collections.emptySet();
}
}
private Set<Element> union(Collection<Set<Element>> elementSets) {
Set<Element> elements = new HashSet<Element>();
for (Set<Element> es : elementSets) {
elements.addAll(es);
}
return elements;
}
}
enum TopLevelNamesKind {
DECLARED_AND_IMPORTS,
DECLARED_AND_EXPORTS
}
/**
* An IdentifierCompleter is used to classify the parent of the completion node when it has
* previously been determined that the completion node is a SimpleIdentifier.
*/
private class IdentifierCompleter extends AstNodeClassifier {
SimpleIdentifier completionNode;
IdentifierCompleter(SimpleIdentifier node) {
completionNode = node;
}
@Override
public Void visitAnnotation(Annotation node) {
if (completionNode instanceof SimpleIdentifier) {
analyzeAnnotationName(completionNode);
}
return null;
}
@Override
public Void visitArgumentList(ArgumentList node) {
if (completionNode instanceof SimpleIdentifier) {
if (isCompletionBetween(
node.getLeftParenthesis().getEnd(),
node.getRightParenthesis().getOffset())) {
analyzeLocalName(completionNode);
analyzePositionalArgument(node, completionNode);
analyzeNamedParameter(node, completionNode);
}
}
return null;
}
@Override
public Void visitAssignmentExpression(AssignmentExpression node) {
if (completionNode instanceof SimpleIdentifier) {
analyzeLocalName(completionNode);
}
return null;
}
@Override
public Void visitBinaryExpression(BinaryExpression node) {
if (node.getLeftOperand() == completionNode) {
analyzeLocalName(completionNode);
} else if (node.getRightOperand() == completionNode) {
analyzeLocalName(completionNode);
}
return null;
}
@Override
public Void visitCombinator(Combinator node) {
proposeCombinator(node, completionNode);
return null;
}
@Override
public Void visitCommentReference(CommentReference node) {
AstNode comment = node.getParent();
CommentReferenceCompleter visitor = new CommentReferenceCompleter(completionNode);
return comment.accept(visitor);
}
@Override
public Void visitConstructorDeclaration(ConstructorDeclaration node) {
if (node.getReturnType() == completionNode) {
filter = createFilter(completionNode);
pName(completionNode.getName(), ProposalKind.CONSTRUCTOR);
}
return null;
}
@Override
public Void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
// { A() : this.!x = 1; }
if (node.getFieldName() == completionNode) {
ClassElement classElement = ((ConstructorDeclaration) node.getParent()).getElement().getEnclosingElement();
fieldReference(classElement, node.getFieldName());
}
return null;
}
@Override
public Void visitConstructorName(ConstructorName node) {
if (node.getName() == completionNode) {
// { new A.!c(); }
TypeName typeName = node.getType();
if (typeName != null) {
Type type = typeName.getType();
Element typeElement = type.getElement();
if (typeElement instanceof ClassElement) {
ClassElement classElement = (ClassElement) typeElement;
constructorReference(classElement, node.getName());
}
}
}
return null;
}
@Override
public Void visitDoStatement(DoStatement node) {
if (node.getCondition() == completionNode) {
analyzeLocalName(completionNode);
}
return null;
}
@Override
public Void visitExpression(Expression node) {
SimpleIdentifier ident;
if (completionNode instanceof SimpleIdentifier) {
ident = completionNode;
} else {
ident = createIdent(node);
}
analyzeLocalName(ident);
return null;
}
@Override
public Void visitExpressionFunctionBody(ExpressionFunctionBody node) {
if (completionNode == node.getExpression()) {
analyzeLocalName(completionNode);
}
return null;
}
@Override
public Void visitExpressionStatement(ExpressionStatement node) {
SimpleIdentifier ident;
if (completionNode instanceof SimpleIdentifier) {
ident = completionNode;
} else {
ident = createIdent(node);
}
analyzeLocalName(ident);
return null;
}
@Override
public Void visitFieldFormalParameter(FieldFormalParameter node) {
if (completionNode == node.getIdentifier()) {
analyzeImmediateField(node.getIdentifier());
}
return null;
}
@Override
public Void visitForEachStatement(ForEachStatement node) {
if (node.getIterator() == completionNode) {
analyzeLocalName(completionNode);
}
return null;
}
@Override
public Void visitFunctionTypeAlias(FunctionTypeAlias node) {
if (node.getName() == completionNode) {
if (node.getReturnType() == null) {
// This may be an incomplete class type alias
state.includesUndefinedTypes();
analyzeTypeName(node.getName(), typeDeclarationName(node));
}
}
return null;
}
@Override
public Void visitIfStatement(IfStatement node) {
if (node.getCondition() == completionNode) {
// { if (!) }
analyzeLocalName(new Ident(node, completionNode.getToken()));
}
return null;
}
@Override
public Void visitInterpolationExpression(InterpolationExpression node) {
if (node.getExpression() instanceof SimpleIdentifier) {
SimpleIdentifier ident = (SimpleIdentifier) node.getExpression();
analyzeLocalName(ident);
}
return null;
}
@Override
public Void visitLibraryIdentifier(LibraryIdentifier node) {
// Library identifiers are always unique, so don't complete them.
return null;
}
@Override
public Void visitMethodDeclaration(MethodDeclaration node) {
if (completionNode == node.getName()) {
if (node.getReturnType() == null) {
// class Foo {const F!(); }
analyzeLocalName(completionNode); // TODO: This is too general; need to restrict to types when following const
}
}
return null;
}
@Override
public Void visitMethodInvocation(MethodInvocation node) {
if (node.getMethodName() == completionNode) {
// { x.!y() }
Expression expr = node.getRealTarget();
Type receiverType;
if (expr == null) { // use this
receiverType = typeOfContainingClass(node);
analyzeDirectAccess(receiverType, node.getMethodName());
} else {
dispatchPrefixAnalysis(node);
}
} else if (node.getTarget() == completionNode) {
// { x!.y() } -- only reached when node.getTarget() is a simple identifier.
if (completionNode instanceof SimpleIdentifier) {
SimpleIdentifier ident = completionNode;
analyzeReceiver(ident);
}
}
return null;
}
@Override
public Void visitParenthesizedExpression(ParenthesizedExpression node) {
// Incomplete closure: foo((Str!)); We check if "()" is argument for function typed parameter.
if (node.getParent() instanceof ArgumentList) {
ParameterElement parameterElement = node.getBestParameterElement();
if (parameterElement != null && parameterElement.getType() instanceof FunctionType) {
Ident ident = createIdent(completionNode);
analyzeTypeName(completionNode, ident);
}
}
return super.visitParenthesizedExpression(node);
}
@Override
public Void visitPrefixedIdentifier(PrefixedIdentifier node) {
if (node.getPrefix() == completionNode) {
// { x!.y }
analyzeLocalName(node.getPrefix());
} else {
// { v.! }
dispatchPrefixAnalysis(node, node.getIdentifier());
}
return null;
}
@Override
public Void visitPropertyAccess(PropertyAccess node) {
if (node.getTarget() != null && node.getTarget().getLength() == 0) {
return null; // { . }
}
// { o.!hashCode }
if (node.getPropertyName() == completionNode) {
analyzePrefixedAccess(node.getRealTarget(), node.getPropertyName());
}
return null;
}
@Override
public Void visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) {
// { A.Fac() : this.!b(); }
if (node.getConstructorName() == completionNode) {
ClassElement classElement = node.getStaticElement().getEnclosingElement();
constructorReference(classElement, node.getConstructorName());
}
return null;
}
@Override
public Void visitReturnStatement(ReturnStatement node) {
if (completionNode instanceof SimpleIdentifier) {
analyzeLocalName(completionNode);
}
return null;
}
@Override
public Void visitSimpleFormalParameter(SimpleFormalParameter node) {
if (node.getIdentifier() == completionNode) {
if (node.getKeyword() == null && node.getType() == null) {
Ident ident = createIdent(node);
analyzeTypeName(node.getIdentifier(), ident);
}
}
return null;
}
@Override
public Void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
analyzeSuperConstructorInvocation(node);
return null;
}
@Override
public Void visitSwitchCase(SwitchCase node) {
if (completionNode == node.getExpression()) {
analyzeLocalName(completionNode);
}
return null;
}
@Override
public Void visitSwitchStatement(SwitchStatement node) {
if (node.getExpression() == completionNode) {
analyzeLocalName(completionNode);
}
return null;
}
@Override
public Void visitTypeName(TypeName node) {
AstNode parent = node.getParent();
if (parent != null) {
TypeNameCompleter visitor = new TypeNameCompleter(completionNode, node);
return parent.accept(visitor);
}
return null;
}
@Override
public Void visitTypeParameter(TypeParameter node) {
// { X<!Y> }
if (isCompletionBetween(node.getOffset(), node.getEnd())) {
analyzeTypeName(completionNode, typeDeclarationName(node));
}
return null;
}
@Override
public Void visitVariableDeclaration(VariableDeclaration node) {
if (node.getName() == completionNode) {
analyzeDeclarationName(node);
} else if (node.getInitializer() == completionNode) {
analyzeLocalName((SimpleIdentifier) node.getInitializer());
}
return null;
}
@Override
public Void visitWhileStatement(WhileStatement node) {
if (node.getCondition() == completionNode) {
analyzeLocalName(completionNode);
}
return null;
}
}
/**
* An StringCompleter is used to classify the parent of the completion node when it has previously
* been determined that the completion node is a SimpleStringLiteral.
*/
private class StringCompleter extends AstNodeClassifier {
SimpleStringLiteral completionNode;
StringCompleter(SimpleStringLiteral node) {
completionNode = node;
}
@Override
public Void visitNamespaceDirective(NamespaceDirective node) {
if (completionNode == node.getUri()) {
namespaceReference(node, completionNode);
}
return null;
}
}
/**
* A TerminalNodeCompleter is used to classify the completion node when nothing else is known
* about it.
*/
private class TerminalNodeCompleter extends AstNodeClassifier {
@Override
public Void visitArgumentList(ArgumentList node) {
if (node.getArguments().isEmpty()
&& isCompletionBetween(
node.getLeftParenthesis().getEnd(),
node.getRightParenthesis().getOffset())) {
if (node.getParent() instanceof MethodInvocation) {
// or node.getParent().accept(this); ?
MethodInvocation invokeNode = (MethodInvocation) node.getParent();
SimpleIdentifier methodName = invokeNode.getMethodName();
ProposalCollector proposalRequestor = new ProposalCollector(requestor);
try {
requestor = proposalRequestor;
dispatchPrefixAnalysis(invokeNode);
} finally {
requestor = proposalRequestor.getRequestor();
}
int offset = methodName.getOffset();
int len = node.getRightParenthesis().getEnd() - offset;
String name = methodName.getName();
for (CompletionProposal proposal : proposalRequestor.getProposals()) {
if (proposal.getCompletion().equals(name)) {
pArgumentList(proposal, offset, len);
}
}
} else if (node.getParent() instanceof InstanceCreationExpression) {
InstanceCreationExpression invokeNode = (InstanceCreationExpression) node.getParent();
ConstructorName methodName = invokeNode.getConstructorName();
ProposalCollector proposalRequestor = new ProposalCollector(requestor);
try {
requestor = proposalRequestor;
dispatchPrefixAnalysis(invokeNode);
} finally {
requestor = proposalRequestor.getRequestor();
}
int offset = methodName.getOffset();
int len = node.getRightParenthesis().getEnd() - offset;
for (CompletionProposal proposal : proposalRequestor.getProposals()) {
if (Objects.equal(proposal.getElement(), invokeNode.getStaticElement())) {
pArgumentList(proposal, offset, len);
}
}
} else if (node.getParent() instanceof Annotation) {
Annotation annotation = (Annotation) node.getParent();
Element annotationElement = annotation.getElement();
if (annotationElement instanceof ConstructorElement) {
ConstructorElement constructorElement = (ConstructorElement) annotationElement;
// we don't need any filter
filter = new Filter("", -1, 0);
// fill parameters for "pArgumentList"
CompletionProposal prop = createProposal(constructorElement);
setParameterInfo(constructorElement.getType(), prop);
prop.setCompletion(constructorElement.getEnclosingElement().getName());
// propose the whole parameters list
pArgumentList(prop, 0, 0);
}
}
}
if (isCompletionBetween(
node.getLeftParenthesis().getEnd(),
node.getRightParenthesis().getOffset())) {
Ident ident = createIdent(node);
analyzeLocalName(ident);
analyzePositionalArgument(node, ident);
analyzeNamedParameter(node, ident);
}
return null;
}
@Override
public Void visitAsExpression(AsExpression node) {
if (isCompletionAfter(node.getAsOperator().getEnd())) {
state.isDynamicAllowed = false;
state.isVoidAllowed = false;
analyzeTypeName(createIdent(node), null);
}
return null;
}
@Override
public Void visitAssertStatement(AssertStatement node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
}
return null;
}
@Override
public Void visitBlock(Block node) {
if (isCompletionBetween(node.getLeftBracket().getEnd(), node.getRightBracket().getOffset())) {
// { {! stmt; !} }
analyzeLocalName(createIdent(node));
}
return null;
}
@Override
public Void visitBooleanLiteral(BooleanLiteral node) {
analyzeLiteralReference(node);
return null;
}
@Override
public Void visitBreakStatement(BreakStatement node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
}
return null;
}
@Override
public Void visitCatchClause(CatchClause node) {
if (isCompletingKeyword(node.getOnKeyword())) {
pKeyword(node.getOnKeyword());
} else if (isCompletingKeyword(node.getCatchKeyword())) {
pKeyword(node.getCatchKeyword());
}
return null;
}
@Override
public Void visitClassDeclaration(ClassDeclaration node) {
if (isCompletingKeyword(node.getClassKeyword())) {
pKeyword(node.getClassKeyword()); // Other keywords are legal but not handled here.
} else if (isCompletingKeyword(node.getAbstractKeyword())) {
pKeyword(node.getAbstractKeyword());
} else if (!node.getLeftBracket().isSynthetic()) {
if (isCompletionAfter(node.getLeftBracket().getEnd())) {
if (node.getRightBracket().isSynthetic()
|| isCompletionBefore(node.getRightBracket().getOffset())) {
if (!hasErrorBeforeCompletionLocation()) {
analyzeLocalName(createIdent(node));
}
}
}
}
// TODO { abstract ! class ! A ! extends B implements C, D ! {}}
return null;
}
@Override
public Void visitClassTypeAlias(ClassTypeAlias node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
}
// TODO { typedef ! A ! = ! B ! with C, D !; }
return null;
}
@Override
public Void visitCombinator(Combinator node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
}
return null;
}
@Override
public Void visitCompilationUnit(CompilationUnit node) {
// This is not a good terminal node...
return null;
}
@Override
public Void visitConstructorName(ConstructorName node) {
// { new A.!c(); }
TypeName typeName = node.getType();
if (typeName != null) {
Type type = typeName.getType();
Element typeElement = type.getElement();
if (typeElement instanceof ClassElement) {
ClassElement classElement = (ClassElement) typeElement;
constructorReference(classElement, node.getName());
}
}
return null;
}
@Override
public Void visitContinueStatement(ContinueStatement node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
}
return null;
}
@Override
public Void visitDirective(Directive node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
}
return null;
}
@Override
public Void visitDoStatement(DoStatement node) {
if (isCompletingKeyword(node.getDoKeyword())) {
pKeyword(node.getDoKeyword());
} else if (isCompletingKeyword(node.getWhileKeyword())) {
pKeyword(node.getWhileKeyword());
} else if (isCompletionBetween(
node.getCondition().getEnd(),
node.getRightParenthesis().getOffset())) {
operatorAccess(node.getCondition(), createIdent(node));
}
return null;
}
@Override
public Void visitDoubleLiteral(DoubleLiteral node) {
return null;
}
@Override
public Void visitExportDirective(ExportDirective node) {
visitNamespaceDirective(node);
return null;
}
@Override
public Void visitExpression(Expression node) {
analyzeLocalName(createIdent(node));
return null;
}
@Override
public Void visitExpressionFunctionBody(ExpressionFunctionBody node) {
if (node.getExpression() != null && node.getSemicolon() != null) {
if (isCompletionBetween(node.getExpression().getEnd(), node.getSemicolon().getOffset())) {
operatorAccess(node.getExpression(), createIdent(node));
}
}
return null;
}
@Override
public Void visitExpressionStatement(ExpressionStatement node) {
analyzeLocalName(createIdent(node));
return null;
}
@Override
public Void visitExtendsClause(ExtendsClause node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
} else if (node.getSuperclass() == null) {
// { X extends ! }
analyzeTypeName(createIdent(node), typeDeclarationName(node));
} else {
// { X extends ! Y }
analyzeTypeName(createIdent(node), typeDeclarationName(node));
}
return null;
}
@Override
public Void visitForEachStatement(ForEachStatement node) {
if (isCompletingKeyword(node.getForKeyword())) {
pKeyword(node.getForKeyword());
} else if (isCompletingKeyword(node.getInKeyword())) {
pKeyword(node.getInKeyword());
}
return null;
}
@Override
public Void visitFormalParameterList(FormalParameterList node) {
if (isCompletionBetween(
node.getLeftParenthesis().getEnd(),
node.getRightParenthesis().getOffset())) {
NodeList<FormalParameter> params = node.getParameters();
if (!params.isEmpty()) {
FormalParameter last = params.get(params.size() - 1);
if (isCompletionBetween(last.getEnd(), node.getRightParenthesis().getOffset())) {
List<FormalParameter> newParams = copyWithout(params, last);
analyzeNewParameterName(newParams, last.getIdentifier(), null);
} else {
Ident ident = createIdent(node);
analyzeTypeName(ident, ident);
}
} else {
Ident ident = createIdent(node);
analyzeTypeName(ident, ident);
}
}
return null;
}
@Override
public Void visitForStatement(ForStatement node) {
if (isCompletingKeyword(node.getForKeyword())) {
pKeyword(node.getForKeyword());
}
return null;
}
@Override
public Void visitFunctionTypeAlias(FunctionTypeAlias node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
}
return null;
}
@Override
public Void visitIfStatement(IfStatement node) {
if (isCompletingKeyword(node.getIfKeyword())) {
pKeyword(node.getIfKeyword());
} else if (isCompletingKeyword(node.getElseKeyword())) {
pKeyword(node.getElseKeyword());
} else if (isCompletionBetween(
node.getCondition().getEnd(),
node.getRightParenthesis().getOffset())) {
operatorAccess(node.getCondition(), createIdent(node));
}
return null;
}
@Override
public Void visitImplementsClause(ImplementsClause node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
} else if (node.getInterfaces().isEmpty()) {
// { X implements ! }
analyzeTypeName(createIdent(node), typeDeclarationName(node));
} else {
// { X implements ! Y }
analyzeTypeName(createIdent(node), typeDeclarationName(node));
}
return null;
}
@Override
public Void visitImportDirective(ImportDirective node) {
if (isCompletingKeyword(node.getAsToken())) {
pKeyword(node.getAsToken());
} else {
visitNamespaceDirective(node);
}
return null;
}
@Override
public Void visitInstanceCreationExpression(InstanceCreationExpression node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
Ident ident = new Ident(node, node.getKeyword());
analyzeLocalName(ident);
} else {
Ident ident = createIdent(node);
analyzeConstructorTypeName(ident);
}
return null;
}
@Override
public Void visitIsExpression(IsExpression node) {
Ident ident;
Token isToken = node.getIsOperator();
int isTokenEnd = isToken.getEnd();
if (isTokenEnd == completionLocation()) {
Expression expression = node.getExpression();
int offset = isToken.getOffset();
// { target.is! } possible name completion, parsed as "target.{synthetic} is!"
if (expression instanceof PrefixedIdentifier) {
PrefixedIdentifier prefIdent = (PrefixedIdentifier) expression;
if (prefIdent.getIdentifier().isSynthetic()) {
analyzePrefixedAccess(prefIdent.getPrefix(), new Ident(node, "is", offset));
} else {
pKeyword(isToken);
}
return null;
}
// { expr is! }
if (!isSyntheticIdentifier(expression)) {
pKeyword(node.getIsOperator());
return null;
}
// { is! } possible name completion
ident = new Ident(node, "is", offset);
} else if (isCompletionAfter(isTokenEnd)) {
state.isDynamicAllowed = false;
state.isVoidAllowed = false;
analyzeTypeName(createIdent(node), null);
return null;
} else {
ident = createIdent(node);
}
analyzeLocalName(ident);
return null;
}
@Override
public Void visitLibraryIdentifier(LibraryIdentifier node) {
// Library identifiers are always unique, so don't complete them.
return null;
}
@Override
public Void visitMethodInvocation(MethodInvocation node) {
Token period = node.getPeriod();
if (period != null && isCompletionAfter(period.getEnd())) {
// { x.!y() }
dispatchPrefixAnalysis(node);
}
return null;
}
@Override
public Void visitNamespaceDirective(NamespaceDirective node) {
StringLiteral uri = node.getUri();
if (uri != null && uri.isSynthetic()
&& node.getKeyword().getEnd() <= context.getSelectionOffset()) {
uri.accept(this);
}
return super.visitNamespaceDirective(node);
}
@Override
public Void visitPartOfDirective(PartOfDirective node) {
if (isCompletingKeyword(node.getOfToken())) {
pKeyword(node.getOfToken());
} else {
visitDirective(node);
}
return null;
}
@Override
public Void visitPrefixedIdentifier(PrefixedIdentifier node) {
if (isCompletionAfter(node.getPeriod().getEnd())) {
if (isCompletionBefore(node.getIdentifier().getOffset())) {
// { x.! } or { x.! y } Note missing/implied semicolon before y; this looks like an
// obscure case but it occurs frequently when editing existing code.
dispatchPrefixAnalysis(node, node.getIdentifier());
}
}
return null;
}
@Override
public Void visitPropertyAccess(PropertyAccess node) {
if (node.getTarget() != null && node.getTarget().getLength() == 0) {
return null; // { . }
}
Expression target = node.getRealTarget();
// The "1 + str.!.length" is parsed as "(1 + str).!.length",
// but actually user wants "1 + (str.!).length".
// So, if completion inside of period-period ".!." then it is not really a cascade completion.
Token operator = node.getOperator();
if (operator.getType() == TokenType.PERIOD_PERIOD) {
int completionLocation = completionLocation();
if (completionLocation > operator.getOffset() && completionLocation < operator.getEnd()) {
while (target instanceof BinaryExpression) {
target = ((BinaryExpression) target).getRightOperand();
}
}
}
// do prefixed completion
analyzePrefixedAccess(target, node.getPropertyName());
return null;
}
@Override
public Void visitReturnStatement(ReturnStatement node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
return null;
}
Expression expression = node.getExpression();
// return !
if (expression instanceof SimpleIdentifier) {
SimpleIdentifier identifier = (SimpleIdentifier) expression;
analyzeLocalName(identifier);
return null;
}
// return expression ! ;
Token semicolon = node.getSemicolon();
if (expression != null && semicolon != null
&& isCompletionBetween(expression.getEnd(), semicolon.getOffset())) {
operatorAccess(expression, createIdent(node));
return null;
}
return null;
}
@Override
public Void visitSimpleFormalParameter(SimpleFormalParameter node) {
if (node.getKeyword() != null && isCompletionBefore(node.getKeyword().getEnd())) {
// f() { g(var! z) }
final Token token = node.getKeyword();
Ident ident = new Ident(node, token);
analyzeTypeName(ident, ident);
}
return null;
}
@Override
public Void visitSimpleIdentifier(SimpleIdentifier node) {
AstNode parent = node.getParent();
if (parent != null) {
IdentifierCompleter visitor = new IdentifierCompleter(node);
return parent.accept(visitor);
}
return null;
}
@Override
public Void visitSimpleStringLiteral(SimpleStringLiteral node) {
AstNode parent = node.getParent();
if (parent instanceof Directive) {
StringCompleter visitor = new StringCompleter(node);
return parent.accept(visitor);
}
return null;
}
@Override
public Void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
analyzeSuperConstructorInvocation(node);
return null;
}
@Override
public Void visitSwitchMember(SwitchMember node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
}
return null;
}
@Override
public Void visitSwitchStatement(SwitchStatement node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
}
return null;
}
@Override
public Void visitTryStatement(TryStatement node) {
if (isCompletingKeyword(node.getTryKeyword())) {
pKeyword(node.getTryKeyword());
}
return null;
}
@Override
public Void visitTypeArgumentList(TypeArgumentList node) {
if (isCompletionBetween(node.getLeftBracket().getEnd(), node.getRightBracket().getOffset())) {
analyzeTypeName(createIdent(node), null);
}
return null;
}
@Override
public Void visitTypeParameter(TypeParameter node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
} else if (node.getName().getName().isEmpty()
&& isCompletionBefore(node.getKeyword().getOffset())) {
// { < ! extends X> }
analyzeTypeName(node.getName(), typeDeclarationName(node));
}
// { <! X ! extends ! Y !> }
return null;
}
@Override
public Void visitTypeParameterList(TypeParameterList node) {
// { <X extends A,! B,! > }
if (isCompletionBetween(node.getLeftBracket().getEnd(), node.getRightBracket().getOffset())) {
analyzeTypeName(createIdent(node), typeDeclarationName(node));
}
return null;
}
@Override
public Void visitVariableDeclaration(VariableDeclaration node) {
if (isCompletionAfter(node.getEquals().getEnd())) {
// { var x =! ...}
analyzeLocalName(createIdent(node));
}
return null;
}
@Override
public Void visitVariableDeclarationList(VariableDeclarationList node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
analyzeTypeName(new Ident(node, node.getKeyword()), null);
}
return null;
}
@Override
public Void visitWhileStatement(WhileStatement node) {
if (isCompletingKeyword(node.getKeyword())) {
pKeyword(node.getKeyword());
} else if (isCompletionBetween(
node.getCondition().getEnd(),
node.getRightParenthesis().getOffset())) {
operatorAccess(node.getCondition(), createIdent(node));
}
return null;
}
@Override
public Void visitWithClause(WithClause node) {
if (isCompletingKeyword(node.getWithKeyword())) {
pKeyword(node.getWithKeyword());
} else if (node.getMixinTypes().isEmpty()) {
// { X with ! }
analyzeTypeName(createIdent(node), typeDeclarationName(node));
} else {
// { X with ! Y }
analyzeTypeName(createIdent(node), typeDeclarationName(node));
}
return null;
}
}
/**
* A TypeNameCompleter is used to classify the parent of a SimpleIdentifier after it has been
* identified as a TypeName by the IdentifierCompleter.
*/
private class TypeNameCompleter extends AstNodeClassifier {
SimpleIdentifier identifier;
TypeName typeName;
TypeNameCompleter(SimpleIdentifier identifier, TypeName typeName) {
this.identifier = identifier;
this.typeName = typeName;
}
@Override
public Void visitAsExpression(AsExpression node) {
if (node.getType() == typeName) {
state.isDynamicAllowed = false;
state.isVoidAllowed = false;
analyzeTypeName(identifier, null);
}
return null;
}
@Override
public Void visitCatchClause(CatchClause node) {
if (node.getExceptionType() == typeName) {
analyzeTypeName(identifier, null);
}
return null;
}
@Override
public Void visitClassTypeAlias(ClassTypeAlias node) {
analyzeTypeName(identifier, typeDeclarationName(node));
return null;
}
@Override
public Void visitConstructorName(ConstructorName node) {
if (typeName == node.getType()) {
if (node.getPeriod() != null) {
if (isCompletionAfter(node.getPeriod().getEnd())) {
// Is this branch reachable? Probably only in IdentifierCompleter.
"".toString(); // TODO This currently is just a place-holder for a breakpoint.
} else {
// { new Cla!ss.cons() }
Element element = identifier.getBestElement();
if (element instanceof ClassElement) {
namedConstructorReference((ClassElement) element, identifier);
}
}
} else {
// { new ! } { new Na!me(); } { new js!on. }
analyzeConstructorTypeName(identifier);
}
}
return null;
}
@Override
public Void visitExtendsClause(ExtendsClause node) {
analyzeTypeName(identifier, typeDeclarationName(node));
return null;
}
@Override
public Void visitFunctionTypeAlias(FunctionTypeAlias node) {
analyzeTypeName(identifier, typeDeclarationName(node));
return null;
}
@Override
public Void visitImplementsClause(ImplementsClause node) {
analyzeTypeName(identifier, typeDeclarationName(node));
return null;
}
@Override
public Void visitIsExpression(IsExpression node) {
if (typeName == node.getType()) {
Token isToken = node.getIsOperator();
if (completionLocation() == isToken.getEnd()) {
Expression expression = node.getExpression();
int offset = isToken.getOffset();
// { target.is! } possible name completion, parsed as "target.{synthetic} is!"
if (expression instanceof PrefixedIdentifier) {
PrefixedIdentifier prefIdent = (PrefixedIdentifier) expression;
if (prefIdent.getIdentifier().isSynthetic()) {
analyzePrefixedAccess(prefIdent.getPrefix(), new Ident(node, "is", offset));
} else {
pKeyword(node.getIsOperator());
}
return null;
}
// { expr is! }
if (!isSyntheticIdentifier(expression)) {
pKeyword(node.getIsOperator());
return null;
}
// { is! } possible name completion
analyzeLocalName(new Ident(node, "is", offset));
} else {
analyzeTypeName((SimpleIdentifier) node.getType().getName(), null);
}
}
return null;
}
@Override
public Void visitMethodDeclaration(MethodDeclaration node) {
if (node.getReturnType() == typeName) {
analyzeTypeName(identifier, null);
}
return null;
}
@Override
public Void visitSimpleFormalParameter(SimpleFormalParameter node) {
analyzeTypeName(identifier, null);
return null;
}
@Override
public Void visitTypeArgumentList(TypeArgumentList node) {
if (isCompletionBetween(node.getLeftBracket().getEnd(), node.getRightBracket().getOffset())) {
analyzeTypeName(identifier, null);
}
return null;
}
@Override
public Void visitTypeParameter(TypeParameter node) {
if (node.getBound() == typeName) {
// { X<A extends !Y> }
analyzeTypeName(identifier, typeDeclarationName(node));
}
return null;
}
@Override
public Void visitVariableDeclarationList(VariableDeclarationList node) {
if (node.getParent() instanceof Statement) {
analyzeLocalName(identifier);
} else {
analyzeTypeName(identifier, null);
}
return null;
}
@Override
public Void visitWithClause(WithClause node) {
analyzeTypeName(identifier, typeDeclarationName(node));
return null;
}
}
private static final String C_DYNAMIC = "dynamic";
private static final String C_FALSE = "false";
private static final String C_NULL = "null";
private static final String C_PARAMNAME = "arg";
private static final String C_TRUE = "true";
private static final String C_VAR = "var";
private static final String C_VOID = "void";
private static boolean isPrivate(Element element) {
String name = element.getDisplayName();
return Identifier.isPrivateName(name);
}
private static boolean isSyntheticIdentifier(Expression expression) {
return expression instanceof SimpleIdentifier && ((SimpleIdentifier) expression).isSynthetic();
}
private CompletionRequestor requestor;
private CompletionFactory factory;
private AssistContext context;
private Filter filter;
private CompletionState state;
private LibraryElement[] libraries;
public CompletionEngine(CompletionRequestor requestor, CompletionFactory factory) {
this.requestor = requestor;
this.factory = factory;
this.state = new CompletionState();
}
/**
* Analyze the source unit in the given context to determine completion proposals at the selection
* offset of the context.
*
* @throws Exception
*/
public void complete(AssistContext context) {
this.context = context;
requestor.beginReporting();
AstNode completionNode = context.getCoveredNode();
if (completionNode != null) {
state.setContext(completionNode);
TerminalNodeCompleter visitor = new TerminalNodeCompleter();
completionNode.accept(visitor);
}
requestor.endReporting();
}
void analyzeAnnotationName(SimpleIdentifier identifier) {
filter = createFilter(identifier);
NameCollector names = collectTopLevelElementVisibleAt(identifier);
for (Element element : names.getUniqueElements()) {
if (element instanceof PropertyAccessorElement) {
element = ((PropertyAccessorElement) element).getVariable();
}
if (element instanceof TopLevelVariableElement) {
TopLevelVariableElement variable = (TopLevelVariableElement) element;
if (state.isCompileTimeConstantRequired && !variable.isConst()) {
continue;
}
proposeName(element, identifier, names);
}
if (element instanceof ClassElement) {
ClassElement classElement = (ClassElement) element;
for (ConstructorElement constructor : classElement.getConstructors()) {
pNamedConstructor(classElement, constructor, identifier);
}
}
}
}
void analyzeConstructorTypeName(SimpleIdentifier identifier) {
filter = createFilter(identifier);
Element[] types = findAllTypes(getCurrentLibrary(), TopLevelNamesKind.DECLARED_AND_IMPORTS);
for (Element type : types) {
if (type instanceof ClassElement) {
namedConstructorReference((ClassElement) type, identifier);
}
}
Element[] prefixes = findAllPrefixes();
for (Element prefix : prefixes) {
pName(prefix, identifier);
}
}
void analyzeDeclarationName(VariableDeclaration varDecl) {
// We might want to propose multiple names for a declaration based on types someday.
// For now, just use whatever is already there.
SimpleIdentifier identifier = varDecl.getName();
filter = createFilter(identifier);
VariableDeclarationList varList = (VariableDeclarationList) varDecl.getParent();
TypeName type = varList.getType();
if (identifier.getLength() > 0) {
pName(identifier.getName(), ProposalKind.VARIABLE);
}
if (type == null) {
if (varList.getKeyword() == null) {
// Interpret as the type name of a typed variable declaration { DivE!; }
analyzeLocalName(identifier);
}
} else {
pParamName(type.getName().getName().toLowerCase());
}
}
void analyzeDirectAccess(Type receiverType, SimpleIdentifier completionNode) {
if (receiverType != null) {
// Complete this.!y where this is absent
Element rcvrTypeElem = receiverType.getElement();
if (receiverType.isDynamic()) {
rcvrTypeElem = getObjectClassElement();
}
if (rcvrTypeElem instanceof ClassElement) {
directAccess((ClassElement) rcvrTypeElem, completionNode);
}
}
}
void analyzeImmediateField(SimpleIdentifier fieldName) {
filter = createFilter(fieldName);
ClassDeclaration classDecl = fieldName.getAncestor(ClassDeclaration.class);
ClassElement classElement = classDecl.getElement();
for (FieldElement field : classElement.getFields()) {
pName(field.getDisplayName(), ProposalKind.FIELD);
}
}
void analyzeLiteralReference(BooleanLiteral literal) {
// state.setContext(literal);
Ident ident = createIdent(literal.getParent());
ident.setToken(literal.getLiteral());
filter = createFilter(ident);
analyzeLocalName(ident);
}
void analyzeLocalName(SimpleIdentifier identifier) {
// Completion x!
filter = createFilter(identifier);
// TODO Filter out types that have no static members.
NameCollector names = collectIdentifiersVisibleAt(identifier);
for (Element element : names.getUniqueElements()) {
if (state.isSourceDeclarationStatic) {
if (element instanceof FieldElement) {
if (!((FieldElement) element).isStatic()) {
continue;
}
} else if (element instanceof PropertyAccessorElement) {
if (!((PropertyAccessorElement) element).isStatic()) {
continue;
}
}
}
if (state.isOptionalArgumentRequired) {
if (!(element instanceof ParameterElement)) {
continue;
}
ParameterElement param = (ParameterElement) element;
if (!param.getParameterKind().isOptional()) {
continue;
}
}
proposeName(element, identifier, names);
}
if (state.areLiteralsAllowed) {
pNull();
pTrue();
pFalse();
}
}
void analyzeNamedParameter(ArgumentList args, SimpleIdentifier identifier) {
// Completion x!
filter = createFilter(identifier);
// prepare parameters
ParameterElement[] parameters = getParameterElements(args);
if (parameters == null) {
return;
}
// remember already used names
Set<String> usedNames = Sets.newHashSet();
for (Expression arg : args.getArguments()) {
if (arg instanceof NamedExpression) {
NamedExpression namedExpr = (NamedExpression) arg;
String name = namedExpr.getName().getLabel().getName();
usedNames.add(name);
}
}
// propose named parameters
for (ParameterElement parameterElement : parameters) {
// should be named
if (parameterElement.getParameterKind() != ParameterKind.NAMED) {
continue;
}
// filter by name
if (filterDisallows(parameterElement)) {
continue;
}
// may be already used
String parameterName = parameterElement.getName();
if (usedNames.contains(parameterName)) {
continue;
}
// OK, add proposal
CompletionProposal prop = createProposal(ProposalKind.NAMED_ARGUMENT);
prop.setCompletion(parameterName);
prop.setParameterName(parameterName);
prop.setParameterType(parameterElement.getType().getDisplayName());
prop.setLocation(identifier.getOffset());
prop.setReplacementLength(identifier.getLength());
prop.setRelevance(CompletionProposal.RELEVANCE_HIGH);
requestor.accept(prop);
}
}
void analyzeNewParameterName(List<FormalParameter> params, SimpleIdentifier typeIdent,
String identifierName) {
String typeName = typeIdent.getName();
filter = createFilter(createIdent(typeIdent));
List<String> names = new ArrayList<String>(params.size());
for (FormalParameter node : params) {
names.add(node.getIdentifier().getName());
}
// Find name similar to typeName not in names, ditto for identifierName.
if (identifierName == null || identifierName.isEmpty()) {
String candidate = typeName == null || typeName.isEmpty() ? C_PARAMNAME
: typeName.toLowerCase();
pParamName(makeNonconflictingName(candidate, names));
} else {
pParamName(makeNonconflictingName(identifierName, names));
if (typeName != null && !typeName.isEmpty()) {
pParamName(makeNonconflictingName(typeName.toLowerCase(), names));
}
}
}
void analyzePositionalArgument(ArgumentList args, SimpleIdentifier identifier) {
// Show parameter name only if there is nothing to complete, so that if there is only
// one match, we won't to force user to choose.
if (!StringUtils.isEmpty(identifier.getName())) {
return;
}
// prepare parameters
ParameterElement[] parameters = getParameterElements(args);
if (parameters == null) {
return;
}
// show current parameter
int argIndex = args.getArguments().indexOf(identifier);
if (argIndex == -1) {
argIndex = 0;
}
if (argIndex >= 0 && argIndex < parameters.length) {
ParameterElement parameter = parameters[argIndex];
if (parameter.getParameterKind() != ParameterKind.NAMED) {
String parameterName = parameter.getDisplayName();
CompletionProposal prop = createProposal(ProposalKind.OPTIONAL_ARGUMENT);
prop.setCompletion(parameterName);
prop.setParameterName(parameterName);
prop.setParameterType(parameter.getType().getDisplayName());
prop.setLocation(identifier.getOffset());
prop.setReplacementLength(identifier.getLength());
prop.setRelevance(CompletionProposal.RELEVANCE_HIGH);
requestor.accept(prop);
}
}
}
void analyzePrefixedAccess(Expression receiver, SimpleIdentifier completionNode) {
if (receiver instanceof ThisExpression && !state.isThisAllowed) {
return;
}
Type receiverType = typeOf(receiver);
boolean forSuper = receiver instanceof SuperExpression;
analyzePrefixedAccess(receiverType, forSuper, completionNode);
}
void analyzePrefixedAccess(Type receiverType, boolean forSuper, SimpleIdentifier completionNode) {
if (receiverType != null) {
// Complete x.!y
Element rcvrTypeElem = receiverType.getElement();
if (receiverType.isBottom() || receiverType.isDynamic()) {
receiverType = getObjectType();
}
if (receiverType instanceof InterfaceType || receiverType instanceof UnionType) {
prefixedAccess(receiverType, forSuper, completionNode);
} else if (rcvrTypeElem instanceof TypeParameterElement) {
TypeParameterElement typeParamElem = (TypeParameterElement) rcvrTypeElem;
analyzePrefixedAccess(typeParamElem.getBound(), false, completionNode);
}
}
}
void analyzeReceiver(SimpleIdentifier identifier) {
// Completion x!.y
filter = createFilter(identifier);
NameCollector names = collectIdentifiersVisibleAt(identifier);
for (Element element : names.getUniqueElements()) {
proposeName(element, identifier, names);
}
}
void analyzeSuperConstructorInvocation(SuperConstructorInvocation node) {
ClassDeclaration enclosingClassNode = node.getAncestor(ClassDeclaration.class);
if (enclosingClassNode != null) {
ClassElement enclosingClassElement = enclosingClassNode.getElement();
if (enclosingClassElement != null) {
ClassElement superClassElement = enclosingClassElement.getSupertype().getElement();
constructorReference(superClassElement, node.getConstructorName());
}
}
}
void analyzeTypeName(SimpleIdentifier identifier, SimpleIdentifier nameIdent) {
filter = createFilter(identifier);
String name = nameIdent == null ? "" : nameIdent.getName();
Element[] types = findAllTypes(getCurrentLibrary(), TopLevelNamesKind.DECLARED_AND_IMPORTS);
for (Element type : types) {
if (state.isForMixin) {
if (!(type instanceof ClassElement)) {
continue;
}
ClassElement classElement = (ClassElement) type;
if (!classElement.isValidMixin()) {
continue;
}
}
if (type.getDisplayName().equals(name)) {
continue;
}
pName(type, nameIdent);
}
if (!state.isForMixin) {
ClassDeclaration classDecl = identifier.getAncestor(ClassDeclaration.class);
if (classDecl != null) {
ClassElement classElement = classDecl.getElement();
for (TypeParameterElement param : classElement.getTypeParameters()) {
pName(param, nameIdent);
}
}
}
Element[] prefixes = findAllPrefixes();
for (Element prefix : prefixes) {
pName(prefix, nameIdent);
}
if (state.isDynamicAllowed) {
pDynamic();
}
if (state.isVarAllowed) {
pVar();
}
if (state.isVoidAllowed) {
pVoid();
}
}
void constructorReference(ClassElement classElement, SimpleIdentifier identifier) {
// Complete identifier when it refers to a constructor defined in classElement.
filter = createFilter(identifier);
for (ConstructorElement cons : classElement.getConstructors()) {
if (state.isCompileTimeConstantRequired == cons.isConst() && filterAllows(cons)) {
pExecutable(cons, identifier, false);
}
}
}
void directAccess(ClassElement classElement, SimpleIdentifier identifier) {
filter = createFilter(identifier);
NameCollector names = createNameCollector();
names.addLocalNames(identifier);
names.addNamesDefinedByHierarchy(classElement, false);
names.addTopLevelNames(getCurrentLibrary(), TopLevelNamesKind.DECLARED_AND_IMPORTS);
proposeNames(names, identifier);
}
void dispatchPrefixAnalysis(InstanceCreationExpression node) {
// prepare ClassElement
ClassElement classElement;
{
Element typeElement = typeOf(node).getElement();
if (!(typeElement instanceof ClassElement)) {
return;
}
classElement = (ClassElement) typeElement;
}
// prepare constructor name
Identifier typeName = node.getConstructorName().getType().getName();
SimpleIdentifier identifier = null;
if (typeName instanceof SimpleIdentifier) {
identifier = (SimpleIdentifier) typeName;
} else if (typeName instanceof PrefixedIdentifier) {
identifier = ((PrefixedIdentifier) typeName).getIdentifier();
}
if (identifier == null) {
identifier = createIdent(node);
}
// analyze constructor name
analyzeConstructorTypeName(identifier);
constructorReference(classElement, identifier);
}
void dispatchPrefixAnalysis(MethodInvocation node) {
// This might be a library prefix on a top-level function
Expression expr = node.getRealTarget();
if (expr instanceof SimpleIdentifier) {
SimpleIdentifier ident = (SimpleIdentifier) expr;
if (ident.getBestElement() instanceof PrefixElement) {
prefixedAccess(ident, node.getMethodName());
return;
} else if (ident.getBestElement() instanceof ClassElement) {
state.areInstanceReferencesProhibited = true;
state.areStaticReferencesProhibited = false;
} else {
state.areInstanceReferencesProhibited = false;
state.areStaticReferencesProhibited = true;
}
}
if (expr == null) {
analyzeLocalName(createIdent(node));
} else {
analyzePrefixedAccess(expr, node.getMethodName());
}
}
void dispatchPrefixAnalysis(PrefixedIdentifier node, SimpleIdentifier identifier) {
SimpleIdentifier receiverName = node.getPrefix();
Element receiver = receiverName.getBestElement();
if (receiver == null) {
prefixedAccess(receiverName, identifier);
return;
}
switch (receiver.getKind()) {
case PREFIX:
case IMPORT:
// Complete lib_prefix.name
prefixedAccess(receiverName, identifier);
break;
default: {
Type receiverType;
Type propType = typeOf(receiverName);
if (propType == null || propType.isDynamic()) {
receiverType = typeOf(receiver);
} else {
Type declType = typeOf(receiver);
if (propType.isMoreSpecificThan(declType)) {
receiverType = propType;
} else {
receiverType = declType;
}
}
analyzePrefixedAccess(receiverType, false, identifier);
break;
}
}
}
void fieldReference(ClassElement classElement, SimpleIdentifier identifier) {
// Complete identifier when it refers to a constructor defined in classElement.
filter = createFilter(identifier);
for (FieldElement cons : classElement.getFields()) {
if (filterAllows(cons)) {
pField(cons, identifier, classElement);
}
}
}
void namedConstructorReference(ClassElement classElement, SimpleIdentifier identifier) {
// Complete identifier when it refers to a named constructor defined in classElement.
if (filter == null) {
filter = createFilter(identifier);
}
for (ConstructorElement cons : classElement.getConstructors()) {
if (!isVisible(cons)) {
continue;
}
if (state.isCompileTimeConstantRequired && !cons.isConst()) {
continue;
}
pNamedConstructor(classElement, cons, identifier);
}
}
void namespacePubReference(NamespaceDirective node, Set<String> packageUris) {
// no import URI or package:
String prefix = filter.prefix;
String[] prefixStrings = prefix.split(":");
if (!prefix.isEmpty() && !"package:".startsWith(prefixStrings[0])) {
return;
}
// if no URI yet, propose package:
if (prefix.isEmpty()) {
pImportUriWithScheme(node, "package:");
return;
}
// check "packages" folder for package libraries that are not added to AnalysisContext
{
Source contextSource = context.getSource();
if (contextSource instanceof FileBasedSource) {
FileBasedSource contextFileSource = (FileBasedSource) contextSource;
String contextFilePath = contextFileSource.getFullName();
File contextFile = new File(contextFilePath);
File contextFolder = contextFile.getParentFile();
File contextPackages = new File(contextFolder, "packages");
if (contextPackages.isDirectory()) {
for (File packageFolder : contextPackages.listFiles()) {
String packageName = packageFolder.getName();
String packageLibName = packageName + ".dart";
File packageFile = new File(packageFolder, packageLibName);
if (packageFile.exists() && packageFile.isFile()) {
packageUris.add("package:" + packageName + "/" + packageLibName);
}
}
}
}
}
// add known package: URIs
for (String uri : packageUris) {
if (filterDisallows(uri)) {
continue;
}
CompletionProposal prop = createProposal(ProposalKind.IMPORT);
prop.setCompletion(uri);
// put "lib" before "lib/src"
if (!uri.contains("/src/")) {
prop.setRelevance(CompletionProposal.RELEVANCE_HIGH);
}
// done
requestor.accept(prop);
}
}
void namespaceReference(NamespaceDirective node, SimpleStringLiteral literal) {
String lit = literal.getLiteral().getLexeme();
if (!lit.isEmpty()) {
lit = lit.substring(1, Math.max(lit.length() - 1, 0));
}
filter = createFilter(new Ident(node, lit, literal.getOffset() + 1));
Set<String> packageUris = Sets.newHashSet();
List<LibraryElement> libraries = new ArrayList<LibraryElement>();
List<LibraryElement> librariesInLib = new ArrayList<LibraryElement>();
String currentLibraryName = getCurrentLibrary().getSource().getFullName();
AnalysisContext ac = getAnalysisContext();
Source[] sources = ac.getLibrarySources();
for (Source s : sources) {
String sName = s.getFullName();
// skip current library
if (currentLibraryName.equals(sName)) {
continue;
}
// ".pub-cache/..../unittest-0.8.8/lib/unittest.dart" -> "package:unittest/unittest.dart"
{
URI uri = ac.getSourceFactory().restoreUri(s);
if (uri != null) {
String uriString = uri.toString();
if (uriString.startsWith("package:")) {
packageUris.add(uriString);
}
}
}
LibraryElement lib = ac.getLibraryElement(s);
if (lib == null) {
continue;
} else if (isUnitInLibFolder(lib.getDefiningCompilationUnit())) {
librariesInLib.add(lib);
} else {
libraries.add(lib);
}
}
namespaceSdkReference(node);
namespacePubReference(node, packageUris);
// importPackageReference(node, libraries, librariesInLib);
}
void namespaceSdkReference(NamespaceDirective node) {
String prefix = filter.prefix;
String[] prefixStrings = prefix.split(":");
if (!prefix.isEmpty() && !"dart:".startsWith(prefixStrings[0])) {
return;
}
if (prefix.isEmpty()) {
pImportUriWithScheme(node, "dart:");
return;
}
// add DartSdk libraries
DartSdk dartSdk = getAnalysisContext().getSourceFactory().getDartSdk();
for (SdkLibrary library : dartSdk.getSdkLibraries()) {
String name = library.getShortName();
// ignore internal
if (library.isInternal()) {
continue;
}
// ignore implementation
if (library.isImplementation()) {
continue;
}
// standard libraries name name starting with "dart:"
name = StringUtils.removeStart(name, "dart:");
// ignore private libraries
if (Identifier.isPrivateName(name)) {
continue;
}
// add with "dart:" prefix
pName("dart:" + name, ProposalKind.IMPORT);
}
}
void operatorAccess(Expression expr, SimpleIdentifier identifier) {
state.requiresOperators();
analyzePrefixedAccess(expr, identifier);
}
void prefixedAccess(InterfaceType type, boolean forSuper, SimpleIdentifier identifier) {
// Complete identifier when it refers to field or method in classElement.
filter = createFilter(identifier);
NameCollector names = createNameCollector();
if (state.areInstanceReferencesProhibited) {
names.addNamesDefinedByType(type);
} else {
names.addNamesDefinedByTypeHierarchy(type, forSuper);
}
proposeNames(names, identifier);
}
void prefixedAccess(SimpleIdentifier prefixName, SimpleIdentifier identifier) {
if (filter == null) {
filter = createFilter(identifier);
}
NameCollector names = createNameCollector();
ImportElement[] prefixImports = importsWithName(prefixName);
// Library prefixes do not have a unique AST representation so we need to fudge state vars.
boolean litsAllowed = state.areLiteralsAllowed;
state.areLiteralsAllowed = false;
names.addTopLevelNames(prefixImports, TopLevelNamesKind.DECLARED_AND_EXPORTS);
state.areLiteralsAllowed = litsAllowed;
proposeNames(names, identifier);
}
void prefixedAccess(Type type, boolean forSuper, SimpleIdentifier identifier) {
filter = createFilter(identifier);
NameCollector names = createNameCollector();
if (state.areInstanceReferencesProhibited) {
names.addNamesDefinedByType(type);
} else {
names.addNamesDefinedByTypeHierarchy(type, forSuper);
}
proposeNames(names, identifier);
}
@DartBlockBody({"// TODO(scheglov) translate it", "return [];"})
private InterfaceType[] allSubtypes(final ClassElement classElement) {
SearchEngine engine = context.getSearchEngine();
SearchScope scope = SearchScopeFactory.createUniverseScope();
SearchFilter directSubsOnly = new SearchFilter() {
@Override
public boolean passes(SearchMatch match) {
Element element = match.getElement();
if (element instanceof ClassElement) {
ClassElement clElem = (ClassElement) element;
while (clElem != null) {
InterfaceType ifType = clElem.getSupertype();
if (ifType == null) {
return false;
}
clElem = ifType.getElement();
if (clElem == classElement) {
return true;
}
}
}
return false;
}
};
List<SearchMatch> matches = engine.searchSubtypes(classElement, scope, directSubsOnly);
InterfaceType[] subtypes = new InterfaceType[matches.size()];
int i = 0;
for (SearchMatch match : matches) {
Element element = match.getElement();
if (element instanceof ClassElement) {
subtypes[i++] = ((ClassElement) element).getType();
}
}
return subtypes;
}
private NameCollector collectIdentifiersVisibleAt(AstNode ident) {
NameCollector names = createNameCollector();
ScopedNameFinder finder = new ScopedNameFinder(completionLocation());
ident.accept(finder);
names.addAll(finder.getLocals().values());
Declaration decl = finder.getDeclaration();
if (decl != null && decl.getParent() instanceof ClassDeclaration) {
ClassElement classElement = ((ClassDeclaration) decl.getParent()).getElement();
names.addNamesDefinedByHierarchy(classElement, false);
}
names.addTopLevelNames(getCurrentLibrary(), TopLevelNamesKind.DECLARED_AND_IMPORTS);
return names;
}
private NameCollector collectTopLevelElementVisibleAt(AstNode ident) {
NameCollector names = createNameCollector();
names.addTopLevelNames(getCurrentLibrary(), TopLevelNamesKind.DECLARED_AND_IMPORTS);
return names;
}
private int completionLocation() {
return context.getSelectionOffset();
}
private int completionTokenOffset() {
return completionLocation() - filter.prefix.length();
}
@DartOmit
private SearchScope constructSearchScope() {
if (libraries == null) {
libraries = currentLibraryList();
}
if (libraries != null) {
return SearchScopeFactory.createLibraryScope(libraries);
}
return SearchScopeFactory.createUniverseScope();
}
private <X extends AstNode> List<FormalParameter> copyWithout(NodeList<X> oldList,
final AstNode deletion) {
final List<FormalParameter> newList = new ArrayList<FormalParameter>(oldList.size() - 1);
oldList.accept(new GeneralizingAstVisitor<Void>() {
@Override
public Void visitNode(AstNode node) {
if (node != deletion) {
newList.add((FormalParameter) node);
}
return null;
}
});
return newList;
}
private Filter createFilter(SimpleIdentifier ident) {
return new Filter(ident, context.getSelectionOffset());
}
private Ident createIdent(AstNode node) {
return new Ident(node, completionLocation());
}
private NameCollector createNameCollector() {
return new NameCollector();
}
private CompletionProposal createProposal(Element element) {
String completion = element.getDisplayName();
return createProposal(element, completion);
}
private CompletionProposal createProposal(Element element, SimpleIdentifier identifier) {
// Create a completion proposal for the element: variable, field, class, function.
if (filterDisallows(element)) {
return null;
}
CompletionProposal prop = createProposal(element);
Element container = element.getEnclosingElement();
if (container != null) {
prop.setDeclaringType(container.getDisplayName());
}
Type type = typeOf(element);
if (type != null) {
prop.setReturnType(type.getName());
}
if (identifier != null) {
prop.setReplacementLengthIdentifier(identifier.getLength());
}
return prop;
}
private CompletionProposal createProposal(Element element, String completion) {
ProposalKind kind = proposalKindOf(element);
CompletionProposal prop = createProposal(kind);
prop.setElement(element);
prop.setCompletion(completion);
prop.setDeprecated(isDeprecated(element));
if (isPrivate(element)) {
prop.setRelevance(CompletionProposal.RELEVANCE_LOW);
}
if (filter.isSameCasePrefix(element.getName())) {
prop.incRelevance();
}
return prop;
}
private CompletionProposal createProposal(ProposalKind kind) {
return factory.createCompletionProposal(kind, completionTokenOffset());
}
private LibraryElement[] currentLibraryList() {
Set<LibraryElement> libraries = new HashSet<LibraryElement>();
LibraryElement curLib = getCurrentLibrary();
libraries.add(curLib);
LinkedList<LibraryElement> queue = new LinkedList<LibraryElement>();
Collections.addAll(queue, curLib.getImportedLibraries());
currentLibraryLister(queue, libraries);
return libraries.toArray(new LibraryElement[libraries.size()]);
}
private void currentLibraryLister(LinkedList<LibraryElement> queue, Set<LibraryElement> libraries) {
while (!queue.isEmpty()) {
LibraryElement sourceLib = queue.removeFirst();
libraries.add(sourceLib);
LibraryElement[] expLibs = sourceLib.getExportedLibraries();
for (LibraryElement lib : expLibs) {
if (!libraries.contains(lib)) {
queue.add(lib);
}
}
}
}
private boolean filterAllows(Element element) {
return filter.match(element);
}
private boolean filterDisallows(Element element) {
return !filter.match(element);
}
private boolean filterDisallows(String name) {
return !filter.match(name);
}
private Element[] findAllNotTypes(List<Element> elements) {
elements = Lists.newArrayList(elements);
for (Iterator<Element> I = elements.iterator(); I.hasNext();) {
Element element = I.next();
ElementKind kind = element.getKind();
if (kind == ElementKind.FUNCTION || kind == ElementKind.TOP_LEVEL_VARIABLE
|| kind == ElementKind.GETTER || kind == ElementKind.SETTER) {
continue;
}
I.remove();
}
return elements.toArray(new Element[elements.size()]);
}
private Element[] findAllPrefixes() {
LibraryElement lib = context.getCompilationUnitElement().getEnclosingElement();
return lib.getPrefixes();
}
private Element[] findAllTypes(LibraryElement library, TopLevelNamesKind topKind) {
List<Element> elements = findTopLevelElements(library, topKind);
return findAllTypes(elements);
}
private Element[] findAllTypes(List<Element> elements) {
elements = Lists.newArrayList(elements);
for (Iterator<Element> I = elements.iterator(); I.hasNext();) {
Element element = I.next();
ElementKind kind = element.getKind();
if (kind == ElementKind.CLASS || kind == ElementKind.FUNCTION_TYPE_ALIAS) {
continue;
}
I.remove();
}
return elements.toArray(new Element[elements.size()]);
}
private List<Element> findTopLevelElements(LibraryElement library, TopLevelNamesKind topKind) {
List<Element> elements = Lists.newArrayList();
if (topKind == TopLevelNamesKind.DECLARED_AND_IMPORTS) {
elements.addAll(CorrectionUtils.getTopLevelElements(library));
for (ImportElement imp : library.getImports()) {
elements.addAll(CorrectionUtils.getImportNamespace(imp).values());
}
removeNotMatchingFilter(elements);
}
if (topKind == TopLevelNamesKind.DECLARED_AND_EXPORTS) {
elements.addAll(CorrectionUtils.getExportNamespace(library).values());
removeNotMatchingFilter(elements);
}
return elements;
}
private AnalysisContext getAnalysisContext() {
return context.getCompilationUnitElement().getContext();
}
private LibraryElement getCurrentLibrary() {
return context.getCompilationUnitElement().getEnclosingElement();
}
private FunctionType getFunctionType(Element element) {
if (element instanceof ExecutableElement) {
ExecutableElement executableElement = (ExecutableElement) element;
return executableElement.getType();
}
if (element instanceof VariableElement) {
VariableElement variableElement = (VariableElement) element;
Type type = variableElement.getType();
if (type instanceof FunctionType) {
return (FunctionType) type;
}
}
return null;
}
private ClassElement getObjectClassElement() {
return getTypeProvider().getObjectType().getElement();
}
private InterfaceType getObjectType() {
return getTypeProvider().getObjectType();
}
private ParameterElement[] getParameterElements(ArgumentList args) {
ParameterElement[] parameters = null;
AstNode argsParent = args.getParent();
if (argsParent instanceof MethodInvocation) {
MethodInvocation invocation = (MethodInvocation) argsParent;
Element nameElement = invocation.getMethodName().getStaticElement();
FunctionType functionType = getFunctionType(nameElement);
if (functionType != null) {
parameters = functionType.getParameters();
}
}
if (argsParent instanceof InstanceCreationExpression) {
InstanceCreationExpression creation = (InstanceCreationExpression) argsParent;
ConstructorElement element = creation.getStaticElement();
if (element != null) {
parameters = ((ExecutableElement) element).getParameters();
}
}
if (argsParent instanceof Annotation) {
Annotation annotation = (Annotation) argsParent;
Element element = annotation.getElement();
if (element instanceof ConstructorElement) {
parameters = ((ConstructorElement) element).getParameters();
}
}
return parameters;
}
private TypeProvider getTypeProvider() {
AnalysisContext analysisContext = context.getCompilationUnitElement().getContext();
try {
return ((InternalAnalysisContext) analysisContext).getTypeProvider();
} catch (AnalysisException exception) {
// TODO(brianwilkerson) Figure out the right thing to do if the core cannot be resolved.
return null;
}
}
private boolean hasErrorBeforeCompletionLocation() {
AnalysisError[] errors = context.getErrors();
if (errors == null || errors.length == 0) {
return false;
}
return errors[0].getOffset() <= completionLocation();
}
private ImportElement[] importsWithName(SimpleIdentifier libName) {
String name = libName.getName();
List<ImportElement> imports = Lists.newArrayList();
for (ImportElement imp : getCurrentLibrary().getImports()) {
PrefixElement prefix = imp.getPrefix();
if (prefix != null) {
String impName = prefix.getDisplayName();
if (name.equals(impName)) {
imports.add(imp);
}
}
}
return imports.toArray(new ImportElement[imports.size()]);
}
private boolean isCompletingKeyword(Token keyword) {
if (keyword == null) {
return false;
}
int completionLoc = context.getSelectionOffset();
if (completionLoc >= keyword.getOffset() && completionLoc <= keyword.getEnd()) {
return true;
}
return false;
}
private boolean isCompletionAfter(int loc) {
return loc <= completionLocation();
}
private boolean isCompletionBefore(int loc) {
return completionLocation() <= loc;
}
private boolean isCompletionBetween(int firstLoc, int secondLoc) {
return isCompletionAfter(firstLoc) && isCompletionBefore(secondLoc);
}
private boolean isDeprecated(Element element) {
return element != null && element.isDeprecated();
}
private boolean isInCurrentLibrary(Element element) {
LibraryElement libElement = getCurrentLibrary();
return element.getLibrary() == libElement;
}
private boolean isUnitInLibFolder(CompilationUnitElement cu) {
String pathString = cu.getSource().getFullName();
if (pathString.indexOf("/lib/") == -1) {
return false;
}
return true;
}
private boolean isVisible(Element element) {
return !isPrivate(element) || isInCurrentLibrary(element);
}
private String makeNonconflictingName(String candidate, List<String> names) {
String possibility = candidate;
int count = 0;
loop : while (true) {
String name = count == 0 ? possibility : possibility + count;
for (String conflict : names) {
if (name.equals(conflict)) {
count += 1;
continue loop;
}
}
return name;
}
}
private void pArgumentList(CompletionProposal proposal, int offset, int len) {
// prepare parameters
String[] parameterNames = proposal.getParameterNames();
if (parameterNames.length == 0) {
return;
}
// fill arguments proposal
CompletionProposal prop = createProposal(ProposalKind.ARGUMENT_LIST);
prop.setElement(proposal.getElement());
prop.setCompletion(proposal.getCompletion()).setReturnType(proposal.getReturnType());
prop.setParameterNames(parameterNames);
prop.setParameterTypes(proposal.getParameterTypes());
prop.setParameterStyle(
proposal.getPositionalParameterCount(),
proposal.hasNamed(),
proposal.hasPositional());
prop.setReplacementLength(0).setLocation(completionLocation());
prop.setRelevance(CompletionProposal.RELEVANCE_HIGH);
requestor.accept(prop);
}
private void pDynamic() {
pWord(C_DYNAMIC, ProposalKind.VARIABLE);
}
private void pExecutable(Element element, FunctionType functionType, SimpleIdentifier identifier,
boolean isPotentialMatch) {
// Create a completion proposal for the element: function, method, getter, setter, constructor.
String name = element.getDisplayName();
if (name.isEmpty()) {
return; // Simple constructors are not handled here
}
if (filterDisallows(element)) {
return;
}
if (!isVisible(element)) {
return;
}
// May be we are in argument of function type parameter, propose function reference.
if (state.targetParameter != null) {
Type parameterType = state.targetParameter.getType();
if (parameterType instanceof FunctionType) {
if (functionType.isAssignableTo(parameterType)) {
pName(name, element, CompletionProposal.RELEVANCE_HIGH, ProposalKind.METHOD_NAME);
}
}
}
CompletionProposal prop = createProposal(element);
prop.setPotentialMatch(isPotentialMatch);
if (isPotentialMatch) {
prop.setRelevance(CompletionProposal.RELEVANCE_LOW);
}
setParameterInfo(functionType, prop);
prop.setCompletion(name).setReturnType(functionType.getReturnType().getDisplayName());
// If there is already argument list, then update only method name.
if (identifier.getParent() instanceof MethodInvocation
&& ((MethodInvocation) identifier.getParent()).getArgumentList() != null) {
prop.setKind(ProposalKind.METHOD_NAME);
}
Element container = element.getEnclosingElement();
if (container != null) {
prop.setDeclaringType(container.getDisplayName());
}
requestor.accept(prop);
}
private void pExecutable(ExecutableElement element, SimpleIdentifier identifier,
boolean isPotentialMatch) {
pExecutable(element, element.getType(), identifier, isPotentialMatch);
}
private void pExecutable(VariableElement element, SimpleIdentifier identifier) {
// Create a completion proposal for the element: top-level variable.
String name = element.getDisplayName();
if (name.isEmpty() || filterDisallows(element)) {
return; // Simple constructors are not handled here
}
CompletionProposal prop = createProposal(element);
if (element.getType() != null) {
prop.setReturnType(element.getType().getName());
}
Element container = element.getEnclosingElement();
if (container != null) {
prop.setDeclaringType(container.getDisplayName());
}
if (identifier != null) {
prop.setReplacementLengthIdentifier(identifier.getLength());
}
requestor.accept(prop);
}
private void pFalse() {
pWord(C_FALSE, ProposalKind.VARIABLE);
}
private void pField(FieldElement element, SimpleIdentifier identifier, ClassElement classElement) {
// Create a completion proposal for the element: field only.
if (filterDisallows(element)) {
return;
}
CompletionProposal prop = createProposal(element);
Element container = element.getEnclosingElement();
prop.setDeclaringType(container.getDisplayName());
requestor.accept(prop);
}
/**
* Proposes URI with the given scheme for the given {@link NamespaceDirective}.
*/
private void pImportUriWithScheme(NamespaceDirective node, String uriScheme) {
String newUri = uriScheme + CompletionProposal.CURSOR_MARKER;
if (node.getUri().isSynthetic()) {
newUri = "'" + newUri + "'";
if (node.getSemicolon() == null || node.getSemicolon().isSynthetic()) {
newUri += ";";
}
}
if (context.getSelectionOffset() == node.getKeyword().getEnd()) {
newUri = " " + newUri;
}
pName(newUri, ProposalKind.IMPORT);
}
private void pKeyword(Token keyword) {
filter = new Filter(keyword.getLexeme(), keyword.getOffset(), completionLocation());
// This isn't as useful as it might seem. It only works in the case that completion
// is requested on an existing recognizable keyword.
// TODO: Add keyword proposal kind
CompletionProposal prop = createProposal(ProposalKind.LIBRARY_PREFIX);
prop.setCompletion(keyword.getLexeme());
requestor.accept(prop);
}
private void pName(Element element, SimpleIdentifier identifier) {
CompletionProposal prop = createProposal(element, identifier);
if (prop != null) {
requestor.accept(prop);
}
}
private void pName(String name, Element element, int relevance, ProposalKind kind) {
if (filterDisallows(name)) {
return;
}
CompletionProposal prop = createProposal(kind);
prop.setRelevance(relevance);
prop.setCompletion(name);
prop.setElement(element);
requestor.accept(prop);
}
private void pName(String name, ProposalKind kind) {
if (filterDisallows(name)) {
return;
}
CompletionProposal prop = createProposal(kind);
prop.setCompletion(name);
requestor.accept(prop);
}
private void pNamedConstructor(ClassElement classElement, ConstructorElement element,
SimpleIdentifier identifier) {
// Create a completion proposal for the named constructor.
String name = classElement.getDisplayName();
if (!element.getDisplayName().isEmpty()) {
name += "." + element.getDisplayName();
}
if (filterDisallows(name)) {
return;
}
CompletionProposal prop = createProposal(element, name);
setParameterInfo(element.getType(), prop);
prop.setReturnType(element.getType().getReturnType().getName());
Element container = element.getEnclosingElement();
prop.setDeclaringType(container.getDisplayName());
if (identifier != null) {
prop.setReplacementLengthIdentifier(identifier.getLength());
}
requestor.accept(prop);
}
private void pNull() {
pWord(C_NULL, ProposalKind.VARIABLE);
}
private void pParamName(String name) {
if (filterDisallows(name)) {
return;
}
CompletionProposal prop = createProposal(ProposalKind.PARAMETER);
prop.setCompletion(name);
requestor.accept(prop);
}
private ProposalKind proposalKindOf(Element element) {
ProposalKind kind;
switch (element.getKind()) {
case CONSTRUCTOR:
kind = ProposalKind.CONSTRUCTOR;
break;
case FUNCTION:
kind = ProposalKind.FUNCTION;
break;
case METHOD:
kind = ProposalKind.METHOD;
break;
case GETTER:
kind = ProposalKind.GETTER;
break;
case SETTER:
kind = ProposalKind.SETTER;
break;
case CLASS:
kind = ProposalKind.CLASS;
break;
case FIELD:
kind = ProposalKind.FIELD;
break;
case IMPORT:
kind = ProposalKind.IMPORT;
break;
case PARAMETER:
kind = ProposalKind.PARAMETER;
break;
case PREFIX:
kind = ProposalKind.LIBRARY_PREFIX;
break;
case FUNCTION_TYPE_ALIAS:
kind = ProposalKind.CLASS_ALIAS;
break;
case TYPE_PARAMETER:
kind = ProposalKind.TYPE_PARAMETER;
break;
case LOCAL_VARIABLE:
case TOP_LEVEL_VARIABLE:
kind = ProposalKind.VARIABLE;
break;
default:
throw new IllegalArgumentException();
}
return kind;
}
private void proposeCombinator(Combinator node, SimpleIdentifier identifier) {
filter = createFilter(identifier);
NamespaceDirective directive = (NamespaceDirective) node.getParent();
LibraryElement libraryElement = directive.getUriElement();
if (libraryElement != null) {
// prepare Elements with unique names
NameCollector nameCollector = createNameCollector();
Collection<Element> elements = CorrectionUtils.getExportNamespace(libraryElement).values();
for (Element element : elements) {
if (filterDisallows(element)) {
continue;
}
nameCollector.mergeName(element);
}
// propose each Element
for (Element element : nameCollector.getUniqueElements()) {
CompletionProposal proposal = createProposal(element);
if (proposal.getKind() == ProposalKind.FUNCTION) {
proposal.setKind(ProposalKind.METHOD_NAME);
}
requestor.accept(proposal);
}
}
}
private void proposeName(Element element, SimpleIdentifier identifier, NameCollector names) {
switch (element.getKind()) {
case FUNCTION:
case GETTER:
case METHOD:
case SETTER:
ExecutableElement candidate = (ExecutableElement) element;
pExecutable(candidate, identifier, names.isPotentialMatch(candidate));
break;
case LOCAL_VARIABLE:
case PARAMETER:
case TOP_LEVEL_VARIABLE:
FunctionType functionType = getFunctionType(element);
if (functionType != null) {
pExecutable(element, functionType, identifier, names.isPotentialMatch(element));
} else {
VariableElement var = (VariableElement) element;
pExecutable(var, identifier);
}
break;
case CLASS:
pName(element, identifier);
break;
default:
break;
}
}
private void proposeNames(NameCollector names, SimpleIdentifier identifier) {
for (Element element : names.getUniqueElements()) {
proposeName(element, identifier, names);
}
}
private void pTrue() {
pWord(C_TRUE, ProposalKind.VARIABLE);
}
private void pVar() {
pWord(C_VAR, ProposalKind.VARIABLE);
}
private void pVoid() {
pWord(C_VOID, ProposalKind.VARIABLE);
}
private void pWord(String word, ProposalKind kind) {
if (filterDisallows(word)) {
return;
}
CompletionProposal prop = createProposal(kind);
prop.setCompletion(word);
requestor.accept(prop);
}
private void removeNotMatchingFilter(List<Element> elements) {
if (filter == null) {
return;
}
filter.makePattern();
filter.removeNotMatching(elements);
}
private void setParameterInfo(FunctionType functionType, CompletionProposal prop) {
List<String> params = new ArrayList<String>();
List<String> types = new ArrayList<String>();
boolean named = false, positional = false;
int posCount = 0;
for (ParameterElement param : functionType.getParameters()) {
if (!param.isSynthetic()) {
switch (param.getParameterKind()) {
case REQUIRED:
posCount += 1;
break;
case NAMED:
named = true;
break;
case POSITIONAL:
positional = true;
break;
}
params.add(param.getDisplayName());
types.add(param.getType().toString());
}
}
prop.setParameterNames(params.toArray(new String[params.size()]));
prop.setParameterTypes(types.toArray(new String[types.size()]));
prop.setParameterStyle(posCount, named, positional);
}
// Find the parent declaration of the given node and extract the name of the type it is defining.
private SimpleIdentifier typeDeclarationName(AstNode node) {
AstNode parent = node;
while (parent != null) {
if (parent instanceof ClassDeclaration) {
return ((ClassDeclaration) parent).getName();
}
if (parent instanceof ClassTypeAlias) {
return ((ClassTypeAlias) parent).getName();
}
if (parent instanceof FunctionTypeAlias) {
return ((FunctionTypeAlias) parent).getName();
}
parent = parent.getParent();
}
return null;
}
private Type typeOf(Element receiver) {
Type receiverType;
switch (receiver.getKind()) {
case FIELD:
case PARAMETER:
case LOCAL_VARIABLE:
case TOP_LEVEL_VARIABLE: {
VariableElement receiverElement = (VariableElement) receiver;
receiverType = receiverElement.getType();
break;
}
case GETTER:
PropertyAccessorElement accessor = (PropertyAccessorElement) receiver;
if (accessor.isSynthetic()) {
PropertyInducingElement inducer = accessor.getVariable();
Type inducerType = inducer.getType();
if (inducerType == null || inducerType.isDynamic()) {
receiverType = typeSearch(inducer);
if (receiverType != null) {
break;
}
}
}
FunctionType accType = accessor.getType();
receiverType = accType == null ? null : accType.getReturnType();
break;
case CONSTRUCTOR:
case FUNCTION:
case METHOD:
case SETTER: {
ExecutableElement receiverElement = (ExecutableElement) receiver;
FunctionType funType = receiverElement.getType();
receiverType = funType == null ? null : funType.getReturnType();
break;
}
case CLASS: {
ClassElement receiverElement = (ClassElement) receiver;
receiverType = receiverElement.getType();
break;
}
case DYNAMIC: {
receiverType = DynamicTypeImpl.getInstance();
break;
}
case FUNCTION_TYPE_ALIAS: {
FunctionTypeAliasElement receiverElement = (FunctionTypeAliasElement) receiver;
FunctionType funType = receiverElement.getType();
receiverType = funType == null ? null : funType.getReturnType();
break;
}
default: {
receiverType = null;
break;
}
}
return receiverType;
}
private Type typeOf(Expression expr) {
// Use static type if known.
{
Type staticType = expr.getStaticType();
if (staticType != null && !staticType.isDynamic()) {
return staticType;
}
}
Type type = expr.getBestType();
if (type.isDynamic()) {
final Type[] result = new Type[1];
AstNodeClassifier visitor = new AstNodeClassifier() {
@Override
public Void visitPrefixedIdentifier(PrefixedIdentifier node) {
return visitSimpleIdentifier(node.getIdentifier());
}
@Override
public Void visitSimpleIdentifier(SimpleIdentifier node) {
Element elem = node.getBestElement();
if (elem != null && elem.getKind() == ElementKind.GETTER) {
PropertyAccessorElement accessor = (PropertyAccessorElement) elem;
if (accessor.isSynthetic()) {
PropertyInducingElement var = accessor.getVariable();
result[0] = typeSearch(var);
}
}
return null;
}
};
expr.accept(visitor);
if (result[0] != null) {
return result[0];
}
}
return type;
}
private Type typeOfContainingClass(AstNode node) {
AstNode parent = node;
while (parent != null) {
if (parent instanceof ClassDeclaration) {
return ((ClassDeclaration) parent).getElement().getType();
}
parent = parent.getParent();
}
return DynamicTypeImpl.getInstance();
}
@DartBlockBody({"// TODO(scheglov) translate it", "return null;"})
private Type typeSearch(PropertyInducingElement varElement) {
SearchEngine engine = context.getSearchEngine();
SearchScope scope = constructSearchScope();
Set<Type> matches = engine.searchAssignedTypes(varElement, scope);
if (matches.isEmpty()) {
return null;
}
Iterator<Type> iter = matches.iterator();
Type result = iter.next();
while (iter.hasNext()) {
result = result.getLeastUpperBound(iter.next());
}
return result;
}
}