Rewrote FieldName and OptionField scoping.

Properly scopes globally defined and nested FieldNames.

Removes unnecessary code from OptionField scoping.

Refactors duplicate code from FieldName, LiteralLink, and OptionField
scoping into cached method createNormalizedScopeForIndexedElement().

Removes unnessesary differences between ProtobufImportScope and
ImportScope.

Change-Id: Iaaa881c1152ea54f7a71f7651e2e80fbf080d810
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_ComplexTypeLink_target_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_ComplexTypeLink_target_Test.java
index 80bde8e..6b0b1f5 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_ComplexTypeLink_target_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_ComplexTypeLink_target_Test.java
@@ -28,7 +28,7 @@
 import org.junit.Test;
 
 import com.google.eclipse.protobuf.junit.core.XtextRule;
-import com.google.eclipse.protobuf.protobuf.ComplexType;
+import com.google.eclipse.protobuf.protobuf.Enum;
 import com.google.eclipse.protobuf.protobuf.ComplexTypeLink;
 import com.google.eclipse.protobuf.protobuf.MessageField;
 import com.google.inject.Inject;
@@ -234,13 +234,13 @@
   //   optional Fruit grape = 1;
   // }
   @Test public void should_provide_nearest_ComplexType() {
+    Enum expectedEnum = xtext.find("Fruit", " {", Enum.class);
     MessageField field = xtext.find("grape", " =", MessageField.class);
-    ComplexTypeLink link = (ComplexTypeLink) field.getType();
-    ComplexType scopedEnum = link.getTarget();  // Enum inherits from ComplexType
     IScope scope = scopeProvider.getScope(field, COMPLEX_TYPE_LINK__TARGET);
-    Object expectedEnum =
-        scope.getSingleElement(
-            QualifiedName.create("sample", "proto", "foo", "Fruit")).getEObjectOrProxy();
+    Object scopedEnum =
+        scope
+            .getSingleElement(QualifiedName.create("sample", "proto", "foo", "Fruit"))
+            .getEObjectOrProxy();
     assertEquals(expectedEnum, scopedEnum);
   }
 
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_FieldName_target_with_ExtensionFieldName_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_FieldName_target_with_ExtensionFieldName_Test.java
index f3fcaa3..b003706 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_FieldName_target_with_ExtensionFieldName_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_FieldName_target_with_ExtensionFieldName_Test.java
@@ -23,14 +23,18 @@
 
 import com.google.eclipse.protobuf.junit.core.XtextRule;
 import com.google.eclipse.protobuf.protobuf.ComplexValue;
+import com.google.eclipse.protobuf.protobuf.ComplexValueCurlyBracket;
 import com.google.eclipse.protobuf.protobuf.ComplexValueField;
 import com.google.eclipse.protobuf.protobuf.ExtensionFieldName;
 import com.google.eclipse.protobuf.protobuf.FieldName;
+import com.google.eclipse.protobuf.protobuf.Option;
+import com.google.eclipse.protobuf.protobuf.SimpleValueField;
 import com.google.eclipse.protobuf.protobuf.ValueField;
 import com.google.inject.Inject;
 
 /**
- * Tests for <code>{@link ProtobufScopeProvider#scope_FieldName_target(FieldName, EReference)}</code>.
+ * Tests for <code>{@link ProtobufScopeProvider#scope_FieldName_target(FieldName,
+ * EReference)}</code>.
  *
  * @author alruiz@google.com (Alex Ruiz)
  */
@@ -69,4 +73,43 @@
     IScope scope = scopeProvider.getScope(name, FIELD_NAME__TARGET);
     assertThat(descriptionsIn(scope), contain("google.proto.test.fileopt"));
   }
+
+  // // Create file sample_proto.proto
+  // syntax = "proto2";
+  //
+  // import "google/protobuf/descriptor.proto";
+  //
+  // package google.proto.sample;
+  //
+  // message Aggregate {
+  //   optional string s = 1;
+  //   optional google.protobuf.FileOptions file = 2;
+  // }
+  //
+  // extend google.protobuf.FileOptions {
+  //   optional Aggregate fileopt = 15478479;
+  // }
+
+  // syntax = "proto2";
+  //
+  // import "sample_proto.proto";
+  //
+  // package google.proto.test;
+  //
+  // option (google.proto.sample.fileopt) = {
+  //   file {
+  //     [google.proto.sample.fileopt] {
+  //       s:'FileExtensionAnnotation'
+  //     }
+  //   }
+  // };
+  @Test public void should_provide_sources_for_aggregate_field_from_import() {
+    Option option = xtext.find("fileopt", ")", Option.class);
+    ComplexValueCurlyBracket valueCurlyBracket = (ComplexValueCurlyBracket) option.getValue();
+    ComplexValueField file = (ComplexValueField) valueCurlyBracket.getFields().get(0);
+    ComplexValueField fileopt = (ComplexValueField) file.getValues().get(0).getFields().get(0);
+    SimpleValueField s = (SimpleValueField) fileopt.getValues().get(0).getFields().get(0);
+    IScope scope = scopeProvider.getScope(s.getName(), FIELD_NAME__TARGET);
+    assertThat(descriptionsIn(scope), contain("s"));
+  }
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportScope.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportScope.java
index 15f9a2c..25e8514 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportScope.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportScope.java
@@ -11,6 +11,8 @@
 import static com.google.eclipse.protobuf.model.util.QualifiedNames.removeLeadingDot;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 import org.eclipse.emf.ecore.EClass;
@@ -27,7 +29,7 @@
 import com.google.common.collect.Multimap;
 
 /**
- * {@link ImportScope} that allows additional ImportNormalizers to be added after initialization.
+ * {@link ImportScope} that allows additional ImportNormalizers to be added.
  *
  * @author (atrookey@google.com) Alexander Rookey
  */
@@ -46,6 +48,10 @@
     this.normalizers = removeDuplicates(namespaceResolvers);
   }
 
+  /*
+   * Override {@link ImportScope.getAliasedElements(Iterable<IEObjectDescription>)} to use local
+   * {@link ImportNormalizer} list.
+   */
   @Override
   protected Iterable<IEObjectDescription> getAliasedElements(
       Iterable<IEObjectDescription> candidates) {
@@ -72,7 +78,10 @@
     return keyToDescription.values();
   }
 
-  // TODO (atrookey) Refactor this method for clarity
+  /*
+   * Override {@link ImportScope.getLocalElementsByName(QualifiedName)} to use local
+   * {@link ImportNormalizer} list.
+   */
   @Override
   protected Iterable<IEObjectDescription> getLocalElementsByName(QualifiedName name) {
     List<IEObjectDescription> result = new ArrayList<>();
@@ -84,11 +93,10 @@
         Iterable<IEObjectDescription> resolvedElements =
             importFrom.getExportedObjects(type, resolvedName, isIgnoreCase());
         for (IEObjectDescription resolvedElement : resolvedElements) {
-          if (resolvedQualifiedName == null) {
-            resolvedQualifiedName = resolvedName;
-          } else if (!resolvedQualifiedName.equals(resolvedName)) {
+          if (resolvedQualifiedName == null) resolvedQualifiedName = resolvedName;
+          else if (!resolvedQualifiedName.equals(resolvedName)) {
             if (result.get(0).getEObjectOrProxy() != resolvedElement.getEObjectOrProxy()) {
-              continue;
+              return Collections.emptyList();
             }
           }
           QualifiedName alias = normalizer.deresolve(resolvedElement.getName());
@@ -113,4 +121,8 @@
   public void addNormalizer(ImportNormalizer normalizer) {
     normalizers.add(normalizer);
   }
+
+  public void addAllNormalizers(Collection<ImportNormalizer> normalizer) {
+    normalizers.addAll(normalizer);
+  }
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportedNamespaceAwareLocalScopeProvider.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportedNamespaceAwareLocalScopeProvider.java
index 0b1fbc0..e37db31 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportedNamespaceAwareLocalScopeProvider.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportedNamespaceAwareLocalScopeProvider.java
@@ -8,8 +8,6 @@
  */
 package com.google.eclipse.protobuf.scoping;
 
-import static java.util.Collections.singletonList;
-
 import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.List;
@@ -32,8 +30,7 @@
 import com.google.inject.Inject;
 
 /**
- * A local scope provider for the Protobuf language that
- * understands namespace imports.
+ * A local scope provider for the Protocol Buffer language based on namespace resolvers.
  *
  * @author (atrookey@google.com) Alexander Rookey
  */
@@ -115,68 +112,14 @@
     return doCreateImportNormalizer(importedNamespace, WILDCARD, ignoreCase);
   }
 
-  /**
-   * Creates a {@link ProtobufImportScope} regardless of whether or not
-   * {@code namespaceResolvers} is empty.
-   */
+  /** Makes {@code getLocalElementsScope()} visible to {@link ProtobufScopeProvider} */
   @Override
   protected IScope getLocalElementsScope(
-      IScope parent, final EObject context, final EReference reference) {
-    IScope result = parent;
-    ISelectable allDescriptions = getAllDescriptions(context.eResource());
-    QualifiedName name = getQualifiedNameOfLocalElement(context);
-    boolean ignoreCase = isIgnoreCase(reference);
-    final List<ImportNormalizer> namespaceResolvers =
-        getImportedNamespaceResolvers(context, ignoreCase);
-    if (isRelativeImport() && name != null && !name.isEmpty()) {
-      ImportNormalizer localNormalizer = doCreateImportNormalizer(name, true, ignoreCase);
-      result =
-          createImportScope(
-              result,
-              singletonList(localNormalizer),
-              allDescriptions,
-              reference.getEReferenceType(),
-              isIgnoreCase(reference));
-    }
-    result =
-        createImportScope(
-            result,
-            namespaceResolvers,
-            null,
-            reference.getEReferenceType(),
-            isIgnoreCase(reference));
-    if (name != null) {
-      ImportNormalizer localNormalizer = doCreateImportNormalizer(name, true, ignoreCase);
-      result =
-          createImportScope(
-              result,
-              singletonList(localNormalizer),
-              allDescriptions,
-              reference.getEReferenceType(),
-              isIgnoreCase(reference));
-    }
-    return result;
+      IScope parent, EObject context, EReference reference) {
+    return super.getLocalElementsScope(parent, context, reference);
   }
 
-  /** 
-   * Makes {@code getAllDescriptions()} visible to {@link ProtobufScopeProvider}
-   */
-  @Override
-  protected ISelectable getAllDescriptions(Resource resource) {
-    return super.getAllDescriptions(resource);
-  }
-  /** 
-   * Makes {@code getImportedNamespaceResolvers()} visible to
-   * {@link ProtobufScopeProvider}
-   */
-  @Override
-  protected List<ImportNormalizer> getImportedNamespaceResolvers(
-      EObject context, boolean ignoreCase) {
-    return super.getImportedNamespaceResolvers(context, ignoreCase);
-  }
-  /** 
-   * Makes {@code getResourceScope()} visible to {@link ProtobufScopeProvider}
-   */
+  /** Makes {@code getResourceScope()} visible to {@link ProtobufScopeProvider} */
   @Override
   protected IScope getResourceScope(Resource res, EReference reference) {
     return super.getResourceScope(res, reference);
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java
index 1fb9d27..0efad69 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java
@@ -8,7 +8,6 @@
  */
 package com.google.eclipse.protobuf.scoping;
 
-import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.OPTION_SOURCE__TARGET;
 import static com.google.eclipse.protobuf.util.Encodings.UTF_8;
 import static com.google.eclipse.protobuf.util.Tracer.DEBUG_SCOPING;
 import static com.google.eclipse.protobuf.validation.ProtobufResourceValidator.getScopeProviderTimingCollector;
@@ -29,6 +28,7 @@
 import com.google.eclipse.protobuf.protobuf.CustomOption;
 import com.google.eclipse.protobuf.protobuf.DefaultValueFieldOption;
 import com.google.eclipse.protobuf.protobuf.Enum;
+import com.google.eclipse.protobuf.protobuf.ExtensionFieldName;
 import com.google.eclipse.protobuf.protobuf.FieldName;
 import com.google.eclipse.protobuf.protobuf.FieldOption;
 import com.google.eclipse.protobuf.protobuf.Group;
@@ -62,20 +62,18 @@
 import org.eclipse.emf.ecore.resource.ResourceSet;
 import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.jface.preference.IPreferenceStore;
-import org.eclipse.xtext.linking.impl.LinkingHelper;
 import org.eclipse.xtext.naming.QualifiedName;
-import org.eclipse.xtext.nodemodel.ICompositeNode;
-import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
-import org.eclipse.xtext.resource.IEObjectDescription;
 import org.eclipse.xtext.scoping.IScope;
 import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider;
 import org.eclipse.xtext.scoping.impl.ImportNormalizer;
 import org.eclipse.xtext.ui.editor.preferences.IPreferenceStoreAccess;
 import org.eclipse.xtext.util.IResourceScopeCache;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
 
 /**
  * A scope provider for the Protobuf language.
@@ -83,13 +81,6 @@
  * @author atrookey@google.com (Alexander Rookey)
  */
 public class ProtobufScopeProvider extends AbstractDeclarativeScopeProvider {
-  @Inject private LinkingHelper linkingHelper;
-  @Inject private IPreferenceStoreAccess storeAccess;
-  @Inject private IUriResolver uriResolver;
-  @Inject private IResourceScopeCache cache;
-  @Inject private ProtobufQualifiedNameConverter nameConverter;
-  @Inject private ProtobufQualifiedNameProvider nameProvider;
-
   /**
    * Returns the name of the descriptor.proto message declaring default options. The options must
    * match the option type of {@code context}. For example, if {@code context} is a file option,
@@ -122,33 +113,106 @@
     return getOptionType(context.eContainer());
   }
 
-  /**
-   * Returns the InputStream associated with the resource at location {@code descriptorLocation}.
-   */
-  private InputStream openFile(URI fileLocation) throws IOException {
-    URL url = new URL(fileLocation.toString());
-    return url.openConnection().getInputStream();
+  @Inject private IPreferenceStoreAccess storeAccess;
+  @Inject private IUriResolver uriResolver;
+  @Inject private IResourceScopeCache cache;
+  @Inject private ProtobufQualifiedNameConverter nameConverter;
+  @Inject private ProtobufQualifiedNameProvider nameProvider;
+
+  private ImportNormalizer createImportNormalizerForEObject(EObject element, boolean ignoreCase) {
+    QualifiedName name = nameProvider.getFullyQualifiedName(element);
+    return getLocalScopeProvider().createImportedNamespaceResolver(name.toString(), ignoreCase);
   }
 
-  // TODO (atrookey) Create utility for getting package.
-  private String getPackageOfResource(Resource resource) {
-    return cache.get(
-        "Package",
-        resource,
-        new Provider<String>() {
-          @Override
-          public String get() {
-            Protobuf protobuf;
-            if (resource != null && (protobuf = (Protobuf) resource.getContents().get(0)) != null) {
-              for (EObject content : protobuf.getElements()) {
-                if (content instanceof Package) {
-                  return ((Package) content).getImportedNamespace();
-                }
+  private List<ImportNormalizer> createImportNormalizersForComplexType(
+      ComplexType complexType, boolean ignoreCase) {
+    List<ImportNormalizer> normalizers = new ArrayList<>();
+    normalizers.add(createImportNormalizerForEObject(complexType, ignoreCase));
+    normalizers.addAll(createImportNormalizersForOneOf(complexType.eContents(), ignoreCase));
+    return normalizers;
+  }
+
+  private List<ImportNormalizer> createImportNormalizersForOneOf(
+      EList<EObject> children, boolean ignoreCase) {
+    List<ImportNormalizer> normalizers = new ArrayList<>();
+    for (EObject child : children) {
+      if (child instanceof OneOf) {
+        normalizers.add(createImportNormalizerForEObject(child, ignoreCase));
+        normalizers.addAll(createImportNormalizersForOneOf(child.eContents(), ignoreCase));
+      }
+    }
+    return normalizers;
+  }
+
+  /**
+   * An {@code IndexedElement} can be a MessageField or Group. When scoping types {@code FieldName},
+   * {@code LiteralLink}, or {@code OptionField} that are all related to protocol buffer options, a
+   * scope can be created by traversing the EMF Model to find a suitable {@code IndexedElement}, and
+   * then creating an import normalized scope for the {@code ComplexType} of the {@code
+   * MessageField} or {@code Group}.
+   *
+   * <p>For example: <pre>
+   * enum MyEnum {
+   *   FOO = 1;
+   * }
+   * extend google.protobuf.ServiceOptions {
+   *   optional MyEnum my_service_option = 50005;
+   * }
+   * service MyService {
+   *   option (my_service_option) = FOO;
+   * }
+   * </pre>
+   *
+   * To scope the {@code LiteralLink} {@code FOO} in {@code MyService}, the {@code MessageField}
+   * {@code my_service_option} is found by traversing the model. The method
+   * createNormalizedScopeForIndexedElement(IndexedElement, EReference) creates and returns an
+   * import normalized scope for the type of the {@code MessageField}, {@code MyEnum}.
+   */
+  private IScope createNormalizedScopeForIndexedElement(
+      IndexedElement indexedElement, EReference reference) {
+    HashMap<EReference, IScope> scopeMap =
+        cache.get(
+            indexedElement,
+            indexedElement.eResource(),
+            new Provider<HashMap<EReference, IScope>>() {
+              @Override
+              public HashMap<EReference, IScope> get() {
+                return new HashMap<>();
               }
-            }
-            return "";
-          }
-        });
+            });
+    if (!scopeMap.containsKey(reference)) {
+      IScope scope = null;
+      if (indexedElement instanceof MessageField) {
+        TypeLink typeLink = ((MessageField) indexedElement).getType();
+        if (typeLink instanceof ComplexTypeLink) {
+          ComplexType complexType = ((ComplexTypeLink) typeLink).getTarget();
+          scope = getGlobalScopeProvider().getScope(complexType.eResource(), reference);
+          List<ImportNormalizer> normalizers =
+              createImportNormalizersForComplexType(complexType, false);
+          scope = createProtobufImportScope(scope, complexType, reference);
+          ((ProtobufImportScope) scope).addAllNormalizers(normalizers);
+        }
+      }
+      if (indexedElement instanceof Group) {
+        Group group = (Group) indexedElement;
+        scope = getGlobalScopeProvider().getScope(group.eResource(), reference);
+        ImportNormalizer normalizer = createImportNormalizerForEObject(group, false);
+        scope = createProtobufImportScope(scope, group, reference);
+        ((ProtobufImportScope) scope).addNormalizer(normalizer);
+      }
+      scopeMap.put(reference, scope);
+    }
+    return scopeMap.get(reference);
+  }
+
+  private IScope createProtobufImportScope(IScope parent, EObject context, EReference reference) {
+    IScope scope = parent;
+    if (context.eContainer() == null) {
+      scope = getLocalScopeProvider().getResourceScope(scope, context, reference);
+    } else {
+      scope = createProtobufImportScope(scope, context.eContainer(), reference);
+    }
+    return getLocalScopeProvider().getLocalElementsScope(scope, context, reference);
   }
 
   /** Returns descriptor associated with the current project. */
@@ -176,11 +240,9 @@
     return null;
   }
 
-  /** Returns name of an object as a QualifiedName. */
-  private QualifiedName getEObjectName(EObject object) {
-    ICompositeNode node = NodeModelUtils.getNode(object);
-    String name = linkingHelper.getCrossRefNodeAsString(node, true);
-    return QualifiedName.create(name);
+  /** Returns the global scope provider. */
+  private ProtobufImportUriGlobalScopeProvider getGlobalScopeProvider() {
+    return getLocalScopeProvider().getGlobalScopeProvider();
   }
 
   /** Returns the local scope provider. */
@@ -188,9 +250,25 @@
     return (ProtobufImportedNamespaceAwareLocalScopeProvider) super.getDelegate();
   }
 
-  /** Returns the global scope provider. */
-  private ProtobufImportUriGlobalScopeProvider getGlobalScopeProvider() {
-    return getLocalScopeProvider().getGlobalScopeProvider();
+  // TODO (atrookey) Create utility for getting package.
+  private String getPackageOfResource(Resource resource) {
+    return cache.get(
+        "Package",
+        resource,
+        new Provider<String>() {
+          @Override
+          public String get() {
+            Protobuf protobuf;
+            if (resource != null && (protobuf = (Protobuf) resource.getContents().get(0)) != null) {
+              for (EObject content : protobuf.getElements()) {
+                if (content instanceof Package) {
+                  return ((Package) content).getImportedNamespace();
+                }
+              }
+            }
+            return "";
+          }
+        });
   }
 
   @Override
@@ -205,87 +283,16 @@
     return scope;
   }
 
-  /** Recursively scope {@code FieldName} starting with an {@code OptionSource}. */
-  private IScope getScopeOfFieldName(
-      IScope delegatedScope, OptionSource optionSource, EReference reference) {
-    IScope retval = IScope.NULLSCOPE;
-    IScope parentScope = super.getScope(optionSource, OPTION_SOURCE__TARGET);
-    QualifiedName optionSourceName = getEObjectName(optionSource);
-    IEObjectDescription indexedElementDescription = parentScope.getSingleElement(optionSourceName);
-    if (indexedElementDescription != null) {
-      EObject indexedElement = indexedElementDescription.getEObjectOrProxy();
-      retval = getLocalScopeOfMessageFieldOrGroup(delegatedScope, indexedElement, reference);
-    }
-    return retval;
-  }
-
-  /** Locally scope any children of {@code context} that are of type {@code OneOf}. */
-  private IScope getScopeOfOneOf(IScope scope, EObject context, EReference reference) {
-    IScope result = scope;
-    for (EObject element : context.eContents()) {
-      if (element instanceof OneOf) {
-        result = getLocalScopeProvider().getLocalElementsScope(result, element, reference);
-      }
-    }
-    return result;
-  }
-
-  /** Recursively scope {@code OptionField} starting with an {@code OptionSource}. */
-  private IScope getScopeOfOptionField(
-      IScope scope,
-      OptionSource optionSource,
-      EReference reference,
-      int fieldIndex,
-      EList<OptionField> fields) {
-    if (fieldIndex < 0 || fields.size() <= fieldIndex) {
-      throw new IllegalArgumentException();
-    }
-    IScope retval = scope;
-    IScope parentScope = IScope.NULLSCOPE;
-    QualifiedName name = QualifiedName.EMPTY;
-    if (fieldIndex == 0) {
-      parentScope = super.getScope(optionSource, OPTION_SOURCE__TARGET);
-      name = getEObjectName(optionSource);
-    } else {
-      OptionField parentOptionField = fields.get(fieldIndex - 1);
-      parentScope = getScopeOfOptionField(retval, optionSource, reference, fieldIndex - 1, fields);
-      name = getEObjectName(parentOptionField);
-    }
-    IEObjectDescription indexedElementDescription = parentScope.getSingleElement(name);
-    if (indexedElementDescription != null) {
-      EObject indexedElement = indexedElementDescription.getEObjectOrProxy();
-      retval = getLocalScopeOfMessageFieldOrGroup(retval, indexedElement, reference);
-    }
-    return retval;
-  }
-
   /**
-   * Recursively scope nested OptionFields and FieldNames.
-   *
-   * <p>If {@code IndexedElement} refers to an {@code MessageField}, scope the {@code Message} of
-   * that field. If it refers to a {@code Group}, return the scope of that {@code Group}.
+   * Returns the InputStream associated with the resource at location {@code descriptorLocation}.
    */
-  private IScope getLocalScopeOfMessageFieldOrGroup(
-      IScope parentScope, EObject indexedElement, EReference reference) {
-    IScope retval = IScope.NULLSCOPE;
-    if (indexedElement instanceof MessageField) {
-      TypeLink typeLink = ((MessageField) indexedElement).getType();
-      if (typeLink instanceof ComplexTypeLink) {
-        ComplexType complexType = ((ComplexTypeLink) typeLink).getTarget();
-        IScope result =
-            getLocalScopeProvider().getLocalElementsScope(parentScope, complexType, reference);
-        retval = getScopeOfOneOf(result, complexType, reference);
-      }
-    }
-    if (indexedElement instanceof Group) {
-      retval =
-          getLocalScopeProvider().getLocalElementsScope(parentScope, indexedElement, reference);
-    }
-    return retval;
+  private InputStream openFile(URI fileLocation) throws IOException {
+    URL url = new URL(fileLocation.toString());
+    return url.openConnection().getInputStream();
   }
 
   /**
-   * Recursively scopes the {@code FieldName} starting with the {@code OptionSource}.
+   * Scopes the {@code FieldName}.
    *
    * <p>For example: <pre>
    * message FooOptions {
@@ -302,40 +309,41 @@
    * The {@code NormalFieldName} {@code opt1} contains a cross-reference to {@code FooOptions.opt1}.
    */
   public IScope scope_FieldName_target(FieldName fieldName, EReference reference) {
+    if (fieldName instanceof ExtensionFieldName) {
+      return getLocalScopeProvider().getResourceScope(fieldName.eResource(), reference);
+    }
+    IndexedElement indexedElement = null;
     OptionSource optionSource = null;
     EObject valueField = fieldName.eContainer();
-    if (valueField != null && valueField instanceof ValueField) {
+    if (valueField instanceof ValueField) {
       EObject complexValue = valueField.eContainer();
-      if (complexValue != null && complexValue instanceof ComplexValue) {
+      if (complexValue instanceof ComplexValue) {
         EObject unknownOption = complexValue.eContainer();
-        IScope delegatedScope = super.delegateGetScope(fieldName, reference);
-        if (unknownOption != null && unknownOption instanceof ComplexValueField) {
-          ComplexValueField complexValueField = (ComplexValueField) unknownOption;
-          IScope parentScope = scope_FieldName_target(complexValueField.getName(), reference);
-          IEObjectDescription indexedElementDescription =
-              parentScope.getSingleElement(this.getEObjectName(complexValueField.getName()));
-          // TODO (atrookey) This is only necessary because of ExtensionFieldName.
-          // Could be made more specific.
-          return getLocalScopeOfMessageFieldOrGroup(
-              delegatedScope, indexedElementDescription.getEObjectOrProxy(), reference);
+        if (unknownOption instanceof ComplexValueField) {
+          indexedElement = ((ComplexValueField) unknownOption).getName().getTarget();
         }
-        if (unknownOption != null && unknownOption instanceof NativeFieldOption) {
+        if (unknownOption instanceof NativeFieldOption) {
           NativeFieldOption nativeFieldOption = (NativeFieldOption) unknownOption;
           optionSource = nativeFieldOption.getSource();
         }
-        if (unknownOption != null && unknownOption instanceof CustomFieldOption) {
+        if (unknownOption instanceof CustomFieldOption) {
           CustomFieldOption customFieldOption = (CustomFieldOption) unknownOption;
           optionSource = customFieldOption.getSource();
         }
-        if (unknownOption != null && unknownOption instanceof NativeOption) {
+        if (unknownOption instanceof NativeOption) {
           NativeOption option = (NativeOption) unknownOption;
           optionSource = option.getSource();
         }
-        if (unknownOption != null && unknownOption instanceof CustomOption) {
+        if (unknownOption instanceof CustomOption) {
           CustomOption option = (CustomOption) unknownOption;
           optionSource = option.getSource();
         }
-        return getScopeOfFieldName(delegatedScope, optionSource, reference);
+        if (optionSource != null) {
+          indexedElement = optionSource.getTarget();
+        }
+        if (indexedElement instanceof MessageField) {
+          return createNormalizedScopeForIndexedElement(indexedElement, reference);
+        }
       }
     }
     return null;
@@ -393,39 +401,6 @@
     return createNormalizedScopeForIndexedElement(indexedElement, reference);
   }
 
-  private IScope createNormalizedScopeForIndexedElement(
-      IndexedElement indexedElement, EReference reference) {
-    return cache.get(
-        indexedElement,
-        indexedElement.eResource(),
-        new Provider<IScope>() {
-          @Override
-          public IScope get() {
-            if (indexedElement instanceof MessageField) {
-              TypeLink typeLink = ((MessageField) indexedElement).getType();
-              if (typeLink instanceof ComplexTypeLink) {
-                ComplexType complexType = ((ComplexTypeLink) typeLink).getTarget();
-                if (complexType instanceof Enum) {
-                  IScope scope =
-                      getGlobalScopeProvider().getScope(complexType.eResource(), reference);
-                  ImportNormalizer normalizer = createImportNormalizer(complexType, false);
-                  scope =
-                      getLocalScopeProvider().getLocalElementsScope(scope, complexType, reference);
-                  ((ProtobufImportScope) scope).addNormalizer(normalizer);
-                  return scope;
-                }
-              }
-            }
-            return null;
-          }
-        });
-  }
-
-  private ImportNormalizer createImportNormalizer(EObject element, boolean ignoreCase) {
-    QualifiedName name = nameProvider.getFullyQualifiedName(element);
-    return getLocalScopeProvider().createImportedNamespaceResolver(name.toString(), ignoreCase);
-  }
-
   /**
    * Recursively scopes the {@code OptionField} starting with the {@code OptionSource}.
    *
@@ -462,7 +437,17 @@
       }
       if (optionSource != null && fields != null) {
         int index = fields.indexOf(optionField);
-        return getScopeOfOptionField(scope, optionSource, reference, index, fields);
+        if (index < 0 || fields.size() <= index) {
+          throw new IllegalArgumentException(
+              "index is " + index + " but field.size() is " + fields.size());
+        }
+        IndexedElement indexedElement = null;
+        if (index == 0) {
+          indexedElement = optionSource.getTarget();
+        } else {
+          indexedElement = fields.get(index - 1).getTarget();
+        }
+        return createNormalizedScopeForIndexedElement(indexedElement, reference);
       }
     }
     return scope;
@@ -506,14 +491,4 @@
             });
     return createProtobufImportScope(descriptorScope, optionSource, reference);
   }
-
-  private IScope createProtobufImportScope(IScope parent, EObject context, EReference reference) {
-    IScope scope = parent;
-    if (context.eContainer() == null) {
-      scope = getLocalScopeProvider().getResourceScope(scope, context, reference);
-    } else {
-      scope = createProtobufImportScope(scope, context.eContainer(), reference);
-    }
-    return getLocalScopeProvider().getLocalElementsScope(scope, context, reference);
-  }
 }