com.google.dart.engine.services.completion.CompletionEngine.java Source code

Java tutorial

Introduction

Here is the source code for com.google.dart.engine.services.completion.CompletionEngine.java

Source

/*
 * 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;
    }
}