Rewrote scope_LiteralLink_target() to correctly handle enums. The editor now narrows the scope to the type of the MessageField the enum is being applied to. Also handles enums from imported protos. Change-Id: Iee211a40670d82aa874cddbaecf543aa3b76aecf
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_LiteralLink_target_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_LiteralLink_target_Test.java index 0f703dc..a341d0b 100644 --- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_LiteralLink_target_Test.java +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_LiteralLink_target_Test.java
@@ -179,7 +179,7 @@ // syntax = "proto2"; // package sample.proto; // - // message Example { + // message Sample { // enum Alpha { // BAR = 1; // } @@ -189,20 +189,19 @@ // package example.proto; // import "sample_proto.proto"; // - // message Fruits { - // enum Beta { + // message Example { + // enum Alpha { // BAR = 1; // } - // optional Grape grape = 4 [default = BAR]; + // optional Alpha alpha = 1 [default = BAR]; // } @Test public void should_provide_Literal_nearest_to_option() { - MessageField field = xtext.find("grape", " =", MessageField.class); + MessageField field = xtext.find("alpha", " =", MessageField.class); DefaultValueFieldOption option = (DefaultValueFieldOption) field.getFieldOptions().get(0); LiteralLink link = (LiteralLink) option.getValue(); Enum enumBeta = (Enum)link.getTarget().eContainer(); - Enum beta = xtext.find("Beta", Enum.class); - Literal literal = (Literal) beta.getElements().get(0); - Enum expectedEnum = (Enum) literal.eContainer(); + Message example = xtext.find("Example", Message.class); + Enum expectedEnum = (Enum) example.getElements().get(0); assertEquals(expectedEnum, enumBeta); } @@ -221,7 +220,7 @@ // } // required Status status = 1 [default = SUCCESS]; // } - @Test public void should_provide_Literals_in_nearest_scope_to_default_value_field_option() { + @Test public void should_provide_default_Literals_matching_type_of_field() { MessageField field = xtext.find("status", " =", MessageField.class); DefaultValueFieldOption option = (DefaultValueFieldOption) field.getFieldOptions().get(0); LiteralLink link = (LiteralLink) option.getValue(); @@ -255,6 +254,28 @@ assertThat(descriptionsIn(scope), contain("SUCCESS")); } + // // Create file sample_proto.proto + // syntax = "proto2"; + // package example; + // + // enum Status { + // SUCCESS = 1; + // } + + // syntax = "proto2"; + // package example; + // import "sample_proto.proto"; + // + // message Response { + // required Status status = 1 [default = SUCCESS]; + // } + @Test public void should_provide_Literals_from_imported_proto() { + MessageField field = xtext.find("status", " =", MessageField.class); + DefaultValueFieldOption option = (DefaultValueFieldOption) field.getFieldOptions().get(0); + IScope scope = scopeProvider.getScope(valueOf(option), LITERAL_LINK__TARGET); + assertThat(descriptionsIn(scope), contain("SUCCESS")); + } + private static LiteralLink valueOf(FieldOption option) { return (LiteralLink) option.getValue(); }
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 f3cbf24..0b1fbc0 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
@@ -25,7 +25,6 @@ import org.eclipse.xtext.scoping.IScope; import org.eclipse.xtext.scoping.impl.ImportNormalizer; import org.eclipse.xtext.scoping.impl.ImportScope; -import org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider; import org.eclipse.xtext.scoping.impl.ImportedNamespaceAwareLocalScopeProvider; import org.eclipse.xtext.util.Strings;
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 aa27afe..d8d2522 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
@@ -32,11 +32,13 @@ import com.google.eclipse.protobuf.protobuf.ComplexValueField; import com.google.eclipse.protobuf.protobuf.CustomFieldOption; 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.ExtensibleType; import com.google.eclipse.protobuf.protobuf.FieldName; import com.google.eclipse.protobuf.protobuf.FieldOption; import com.google.eclipse.protobuf.protobuf.Group; +import com.google.eclipse.protobuf.protobuf.IndexedElement; import com.google.eclipse.protobuf.protobuf.Literal; import com.google.eclipse.protobuf.protobuf.LiteralLink; import com.google.eclipse.protobuf.protobuf.Message; @@ -44,6 +46,7 @@ import com.google.eclipse.protobuf.protobuf.NativeFieldOption; import com.google.eclipse.protobuf.protobuf.NativeOption; import com.google.eclipse.protobuf.protobuf.OneOf; +import com.google.eclipse.protobuf.protobuf.Option; import com.google.eclipse.protobuf.protobuf.OptionField; import com.google.eclipse.protobuf.protobuf.OptionSource; import com.google.eclipse.protobuf.protobuf.Package; @@ -81,9 +84,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; /** * A scope provider for the Protobuf language. @@ -436,62 +437,71 @@ * * The {@code LiteralLink} {@code FOO} contains a cross-reference to {@code MyEnum.FOO}. */ - public IScope scope_LiteralLink_target(LiteralLink literalLink, EReference reference) { - IScope scope = IScope.NULLSCOPE; - Resource descriptorResource = getDescriptorResource(literalLink); - EObject descriptor = descriptorResource.getContents().get(0); - scope = createLiteralLinkResolvedScope(reference, scope, descriptor); - scope = createLiteralLinkResolvedScope(reference, scope, literalLink); - return scope; + public @Nullable IScope scope_LiteralLink_target(LiteralLink literalLink, EReference reference) { + EObject container = literalLink.eContainer(); + IndexedElement indexedElement = null; + if (container instanceof DefaultValueFieldOption) { + container = container.eContainer(); + if (container instanceof IndexedElement) { + indexedElement = (IndexedElement) container; + } + } + if (container instanceof NativeFieldOption) { + indexedElement = ((NativeFieldOption) container).getSource().getTarget(); + } + if (container instanceof NativeOption) { + indexedElement = ((NativeOption) container).getSource().getTarget(); + } + if (container instanceof CustomFieldOption) { + EList<OptionField> fields = ((CustomFieldOption) container).getFields(); + if (!fields.isEmpty()) { + indexedElement = fields.get(fields.size() - 1).getTarget(); + } else { + indexedElement = ((CustomFieldOption) container).getSource().getTarget(); + } + } + if (container instanceof CustomOption) { + EList<OptionField> fields = ((CustomOption) container).getFields(); + if (!fields.isEmpty()) { + indexedElement = fields.get(fields.size() - 1).getTarget(); + } else { + indexedElement = ((CustomOption) container).getSource().getTarget(); + } + } + return createNormalizedScopeForIndexedElement(indexedElement, reference); } - private IScope createLiteralLinkResolvedScope( - EReference reference, IScope parent, EObject context) { - if (context == null) { - return parent; - } - final IScope parentScope = - createLiteralLinkResolvedScope(reference, parent, context.eContainer()); + private IScope createNormalizedScopeForIndexedElement( + IndexedElement indexedElement, EReference reference) { return cache.get( - context, - context.eResource(), + indexedElement, + indexedElement.eResource(), new Provider<IScope>() { @Override public IScope get() { - List<ImportNormalizer> protoNormalizers = createEnumElementResolvers(context, false); - IScope scope = getSingleLevelProtobufImport(parentScope, context, reference); - if (scope instanceof ProtobufImportScope) { - for (ImportNormalizer normalizer : protoNormalizers) { - ((ProtobufImportScope) scope).addNormalizer(normalizer); + 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 scope; + return null; } }); } - /** - * Creates an {@code ImportNormalizer} for every {@code Enum} that is a descendant of {@code - * context}. - */ - private List<ImportNormalizer> createEnumElementResolvers(EObject context, boolean ignoreCase) { - List<ImportNormalizer> importedNamespaceResolvers = new ArrayList<>(); - for (EObject child : context.eContents()) { - if (child instanceof Enum) { - QualifiedName name = nameProvider.getFullyQualifiedName(child); - if (name != null && !name.isEmpty()) { - ImportNormalizer resolver = - getLocalScopeProvider().createImportedNamespaceResolver(name.toString(), ignoreCase); - if (resolver != null) { - importedNamespaceResolvers.add(resolver); - } - } - } - if (child instanceof Message) { - importedNamespaceResolvers.addAll(createEnumElementResolvers(child, ignoreCase)); - } - } - return importedNamespaceResolvers; + private ImportNormalizer createImportNormalizer(EObject element, boolean ignoreCase) { + QualifiedName name = nameProvider.getFullyQualifiedName(element); + return getLocalScopeProvider().createImportedNamespaceResolver(name.toString(), ignoreCase); } /** @@ -549,50 +559,39 @@ public IScope scope_OptionSource_target(OptionSource optionSource, EReference reference) { String optionType = getOptionType(optionSource); Resource resource = optionSource.eResource(); - IScope descriptorScope = cache.get( - optionType, - resource, - new Provider<IScope>() { - @Override - public IScope get() { - IScope scope = getGlobalScopeProvider().getScope(resource, reference); - Resource descriptorResource = getDescriptorResource(optionSource); - String descriptorMessage = - getPackageOfResource(descriptorResource) - + nameConverter.getDelimiter() - + optionType; - ImportNormalizer normalizer = - getLocalScopeProvider().createImportedNamespaceResolver(descriptorMessage, false); - scope = - getProtobufImportScope( - scope, getDescriptorResource(optionSource).getContents().get(0), reference); - ((ProtobufImportScope) scope).addNormalizer(normalizer); - return scope; - } - }); - return getProtobufImportScope(descriptorScope, optionSource, reference); + IScope descriptorScope = + cache.get( + optionType, + resource, + new Provider<IScope>() { + @Override + public IScope get() { + IScope scope = getGlobalScopeProvider().getScope(resource, reference); + Resource descriptorResource = getDescriptorResource(optionSource); + String descriptorMessage = + getPackageOfResource(descriptorResource) + + nameConverter.getDelimiter() + + optionType; + ImportNormalizer normalizer = + getLocalScopeProvider() + .createImportedNamespaceResolver(descriptorMessage, false); + scope = + createProtobufImportScope( + scope, getDescriptorResource(optionSource).getContents().get(0), reference); + ((ProtobufImportScope) scope).addNormalizer(normalizer); + return scope; + } + }); + return createProtobufImportScope(descriptorScope, optionSource, reference); } - /** Returns a multi level ProtobufImportScope {@code EObject}. */ - private IScope getProtobufImportScope( - IScope parent, EObject context, EReference 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 = getProtobufImportScope(scope, context.eContainer(), reference); + scope = createProtobufImportScope(scope, context.eContainer(), reference); } return getLocalScopeProvider().getLocalElementsScope(scope, context, reference); } - - /** Returns a single level ProtobufImportScope {@code EObject}. */ - private ProtobufImportScope getSingleLevelProtobufImport( - IScope parent, EObject context, EReference reference) { - IScope scope = parent; - if (context.eContainer() == null) { - scope = getLocalScopeProvider().getResourceScope(scope, context, reference); - } - return (ProtobufImportScope) - getLocalScopeProvider().getLocalElementsScope(scope, context, reference); - } }