Implements new scoping pipeline Removes scoping methods dependent on ModelElementFinder and replaces them with methods dependent on ProtobufImportedNamespaceAwareLocalScopeProvider. Change-Id: I50e8691a61bbb85f277fef6748ed4724c85fdd41
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue131_AddOptionsForService_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue131_AddOptionsForService_Test.java index 401bb86..d924229 100644 --- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue131_AddOptionsForService_Test.java +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue131_AddOptionsForService_Test.java
@@ -13,13 +13,12 @@ import static com.google.eclipse.protobuf.junit.IEObjectDescriptions.descriptionsIn; import static com.google.eclipse.protobuf.junit.core.IntegrationTestModule.integrationTestModule; import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; -import static com.google.eclipse.protobuf.junit.matchers.ContainAllNames.containAll; +import static com.google.eclipse.protobuf.junit.matchers.ContainNames.contain; +import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.OPTION_SOURCE__TARGET; -import org.eclipse.emf.ecore.EReference; import org.eclipse.xtext.scoping.IScope; import org.junit.Rule; import org.junit.Test; - import com.google.eclipse.protobuf.junit.core.XtextRule; import com.google.eclipse.protobuf.protobuf.Option; import com.google.eclipse.protobuf.scoping.ProtobufScopeProvider; @@ -33,7 +32,6 @@ public class Issue131_AddOptionsForService_Test { @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule()); - @Inject private EReference reference; @Inject private ProtobufScopeProvider scopeProvider; // syntax = "proto2"; @@ -52,10 +50,9 @@ // } @Test public void should_support_Service_options() { Option option = xtext.find("code", ")", Option.class); - IScope scope = scopeProvider.scope_OptionSource_target(option.getSource(), reference); - assertThat(descriptionsIn(scope), containAll("code", "proto.code", "google.proto.code", "com.google.proto.code", - ".com.google.proto.code", - "info", "proto.info", "google.proto.info", "com.google.proto.info", - ".com.google.proto.info")); + IScope scope = scopeProvider.getScope(option.getSource(), OPTION_SOURCE__TARGET); + assertThat(descriptionsIn(scope), contain("code", "proto.code", "google.proto.code", + "com.google.proto.code", "info", "proto.info", "google.proto.info", + "com.google.proto.info")); } }
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue147_AddSupportForGroupOptions_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue147_AddSupportForGroupOptions_Test.java index 62a5015..8abafc0 100644 --- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue147_AddSupportForGroupOptions_Test.java +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue147_AddSupportForGroupOptions_Test.java
@@ -12,25 +12,17 @@ import static com.google.eclipse.protobuf.junit.IEObjectDescriptions.descriptionsIn; import static com.google.eclipse.protobuf.junit.core.IntegrationTestModule.integrationTestModule; import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; -import static com.google.eclipse.protobuf.junit.matchers.ContainAllFields.containAll; -import static com.google.eclipse.protobuf.junit.matchers.ContainAllNames.containAll; +import static com.google.eclipse.protobuf.junit.matchers.ContainNames.contain; +import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.OPTION_SOURCE__TARGET; -import java.util.Collection; - -import org.eclipse.emf.ecore.EReference; import org.eclipse.xtext.scoping.IScope; import org.junit.Rule; import org.junit.Test; import com.google.eclipse.protobuf.junit.core.XtextRule; import com.google.eclipse.protobuf.protobuf.CustomFieldOption; -import com.google.eclipse.protobuf.protobuf.Group; -import com.google.eclipse.protobuf.protobuf.MessageField; import com.google.eclipse.protobuf.protobuf.NativeFieldOption; -import com.google.eclipse.protobuf.scoping.ProtoDescriptor; -import com.google.eclipse.protobuf.scoping.ProtoDescriptorProvider; import com.google.eclipse.protobuf.scoping.ProtobufScopeProvider; -import com.google.eclipse.protobuf.util.EResources; import com.google.inject.Inject; /** @@ -41,8 +33,6 @@ public class Issue147_AddSupportForGroupOptions_Test { @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule()); - @Inject private EReference reference; - @Inject private ProtoDescriptorProvider descriptorProvider; @Inject private ProtobufScopeProvider scopeProvider; // syntax = "proto2"; @@ -54,12 +44,10 @@ // } @Test public void should_provide_fields_for_native_option() { NativeFieldOption option = xtext.find("deprecated", NativeFieldOption.class); - IScope scope = scopeProvider.scope_OptionSource_target(option.getSource(), reference); - Group group = xtext.find("membership", Group.class); - ProtoDescriptor descriptor = - descriptorProvider.primaryDescriptor(EResources.getProjectOf(option.eResource())); - Collection<MessageField> optionSources = descriptor.availableOptionsFor(group); - assertThat(descriptionsIn(scope), containAll(optionSources)); + IScope scope = scopeProvider.scope_OptionSource_target(option.getSource(), + OPTION_SOURCE__TARGET); + assertThat(descriptionsIn(scope), contain("ctype", "packed", "deprecated", + "experimental_map_key")); } // syntax = "proto2"; @@ -79,10 +67,10 @@ // } @Test public void should_provide_fields_for_custom_option() { CustomFieldOption option = xtext.find("code", ")", CustomFieldOption.class); - IScope scope = scopeProvider.scope_OptionSource_target(option.getSource(), reference); - assertThat(descriptionsIn(scope), containAll("code", "proto.code", "google.proto.code", "com.google.proto.code", - ".com.google.proto.code", - "info", "proto.info", "google.proto.info", "com.google.proto.info", - ".com.google.proto.info")); + IScope scope = scopeProvider.scope_OptionSource_target(option.getSource(), + OPTION_SOURCE__TARGET); + assertThat(descriptionsIn(scope), contain("code", "proto.code", "google.proto.code", + "com.google.proto.code", "info", "proto.info", "google.proto.info", + "com.google.proto.info")); } }
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue156_AddSupportForEnumValueOptions_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue156_AddSupportForEnumValueOptions_Test.java index 9afbe58..e10ba9f 100644 --- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue156_AddSupportForEnumValueOptions_Test.java +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue156_AddSupportForEnumValueOptions_Test.java
@@ -13,13 +13,12 @@ import static com.google.eclipse.protobuf.junit.IEObjectDescriptions.descriptionsIn; import static com.google.eclipse.protobuf.junit.core.IntegrationTestModule.integrationTestModule; import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; -import static com.google.eclipse.protobuf.junit.matchers.ContainAllNames.containAll; +import static com.google.eclipse.protobuf.junit.matchers.ContainNames.contain; +import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.OPTION_SOURCE__TARGET; -import org.eclipse.emf.ecore.EReference; import org.eclipse.xtext.scoping.IScope; import org.junit.Rule; import org.junit.Test; - import com.google.eclipse.protobuf.junit.core.XtextRule; import com.google.eclipse.protobuf.protobuf.CustomFieldOption; import com.google.eclipse.protobuf.scoping.ProtobufScopeProvider; @@ -33,7 +32,6 @@ public class Issue156_AddSupportForEnumValueOptions_Test { @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule()); - @Inject private EReference reference; @Inject private ProtobufScopeProvider scopeProvider; // syntax = "proto2"; @@ -49,7 +47,7 @@ // } @Test public void should_provide_fields_for_custom_field_option() { CustomFieldOption option = xtext.find("active", ")", CustomFieldOption.class); - IScope scope = scopeProvider.scope_OptionSource_target(option.getSource(), reference); - assertThat(descriptionsIn(scope), containAll("active", ".active")); + IScope scope = scopeProvider.getScope(option.getSource(), OPTION_SOURCE__TARGET); + assertThat(descriptionsIn(scope), contain("active")); } }
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/IntegrationTestModule.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/IntegrationTestModule.java index f66915f..4668d91 100644 --- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/IntegrationTestModule.java +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/IntegrationTestModule.java
@@ -1,5 +1,5 @@ /* -* Copyright (c) 2014 Google Inc. + * Copyright (c) 2014 Google Inc. * * All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse * Public License v1.0 which accompanies this distribution, and is available at @@ -12,12 +12,17 @@ import static org.eclipse.xtext.util.Strings.isEmpty; import com.google.eclipse.protobuf.model.util.Imports; +import com.google.eclipse.protobuf.preferences.general.PreferenceNames; import com.google.eclipse.protobuf.protobuf.Import; import com.google.eclipse.protobuf.scoping.IImportResolver; +import com.google.eclipse.protobuf.scoping.IUriResolver; import com.google.inject.Inject; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EReference; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.xtext.ui.editor.preferences.IPreferenceStoreAccess; +import org.mockito.Mockito; import java.io.File; @@ -31,16 +36,32 @@ return new IntegrationTestModule(); } - private IntegrationTestModule( ) {} + private IntegrationTestModule() {} - @Override protected void configure() { + @Override + protected void configure() { binder().bind(IImportResolver.class).to(ImportResolver.class); mockAndBind(EReference.class); + IPreferenceStoreAccess mockStoreAccess = Mockito.mock(IPreferenceStoreAccess.class); + IUriResolver mockUriResolver = Mockito.mock(IUriResolver.class); + binder().bind(IPreferenceStoreAccess.class).toInstance(mockStoreAccess); + // TODO (atrookey) Get rid of the excessive mocking. + binder().bind(IUriResolver.class).toInstance(mockUriResolver); + IPreferenceStore mockPreferenceStore = Mockito.mock(IPreferenceStore.class); + Mockito.when(mockStoreAccess.getWritablePreferenceStore(Mockito.anyObject())) + .thenReturn(mockPreferenceStore); + Mockito.when(mockPreferenceStore.getString(PreferenceNames.DESCRIPTOR_PROTO_PATH)) + .thenReturn(PreferenceNames.DEFAULT_DESCRIPTOR_PATH); + Mockito.when( + mockUriResolver.resolveUri( + Mockito.eq(PreferenceNames.DEFAULT_DESCRIPTOR_PATH), + Mockito.anyObject(), + Mockito.anyObject())) + .thenReturn("platform:/plugin/com.google.eclipse.protobuf/descriptor.proto"); } private static class ImportResolver implements IImportResolver { - @Inject - private Imports imports; + @Inject private Imports imports; @Override public String resolve(Import anImport) {
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 47e5fb1..80bde8e 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
@@ -8,6 +8,7 @@ */ package com.google.eclipse.protobuf.scoping; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.when; @@ -17,14 +18,17 @@ import static com.google.eclipse.protobuf.junit.matchers.ContainAllNames.containAll; import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.COMPLEX_TYPE; import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.COMPLEX_TYPE_LINK; +import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.COMPLEX_TYPE_LINK__TARGET; import org.eclipse.emf.ecore.EReference; +import org.eclipse.xtext.naming.QualifiedName; import org.eclipse.xtext.scoping.IScope; import org.junit.Before; import org.junit.Rule; 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.ComplexTypeLink; import com.google.eclipse.protobuf.protobuf.MessageField; import com.google.inject.Inject; @@ -67,12 +71,10 @@ @Test public void should_provide_Types() { MessageField field = xtext.find("type", MessageField.class); IScope scope = scopeProvider.getScope(typeOf(field), reference); - assertThat(descriptionsIn(scope), containAll("Type", "proto.Type", "google.proto.Type", "com.google.proto.Type", - ".com.google.proto.Type", - "Address", "proto.Address", "google.proto.Address", - "com.google.proto.Address", ".com.google.proto.Address", - "Contact", "proto.Contact", "google.proto.Contact", - "com.google.proto.Contact", ".com.google.proto.Contact")); + assertThat(descriptionsIn(scope), containAll("Type", "proto.Type", "google.proto.Type", + "com.google.proto.Type", "Address", "proto.Address", "google.proto.Address", + "com.google.proto.Address", "Contact", "proto.Contact", "google.proto.Contact", + "com.google.proto.Contact")); } // // Create file types.proto @@ -103,10 +105,8 @@ @Test public void should_provide_imported_Types() { MessageField field = xtext.find("type", " =", MessageField.class); IScope scope = scopeProvider.getScope(typeOf(field), reference); - assertThat(descriptionsIn(scope), containAll("test.proto.Type", ".test.proto.Type", - "test.proto.Address", ".test.proto.Address", - "Contact", "proto.Contact", "google.proto.Contact", - "com.google.proto.Contact", ".com.google.proto.Contact")); + assertThat(descriptionsIn(scope), containAll("test.proto.Type", "test.proto.Address", + "Contact", "proto.Contact", "google.proto.Contact", "com.google.proto.Contact")); } // // Create file types.proto @@ -137,12 +137,10 @@ @Test public void should_provide_imported_Types_with_equal_package() { MessageField field = xtext.find("type", " =", MessageField.class); IScope scope = scopeProvider.getScope(typeOf(field), reference); - assertThat(descriptionsIn(scope), containAll("Type", "proto.Type", "google.proto.Type", "com.google.proto.Type", - ".com.google.proto.Type", - "Address", "proto.Address", "google.proto.Address", - "com.google.proto.Address", ".com.google.proto.Address", - "Contact", "proto.Contact", "google.proto.Contact", - "com.google.proto.Contact", ".com.google.proto.Contact")); + assertThat(descriptionsIn(scope), containAll("Type", "proto.Type", "google.proto.Type", + "com.google.proto.Type", "Address", "proto.Address", "google.proto.Address", + "com.google.proto.Address", "Contact", "proto.Contact", "google.proto.Contact", + "com.google.proto.Contact")); } // // Create file types.proto @@ -173,10 +171,8 @@ @Test public void should_provide_public_imported_Types() { MessageField field = xtext.find("type", " =", MessageField.class); IScope scope = scopeProvider.getScope(typeOf(field), reference); - assertThat(descriptionsIn(scope), containAll("test.proto.Type", ".test.proto.Type", - "test.proto.Address", ".test.proto.Address", - "Contact", "proto.Contact", "google.proto.Contact", - "com.google.proto.Contact", ".com.google.proto.Contact")); + assertThat(descriptionsIn(scope), containAll("test.proto.Type", "test.proto.Address", + "Contact", "proto.Contact", "google.proto.Contact", "com.google.proto.Contact")); } // // Create file public-types.proto @@ -214,10 +210,38 @@ @Test public void should_provide_public_imported_Types_with_more_than_one_level() { MessageField field = xtext.find("type", " =", MessageField.class); IScope scope = scopeProvider.getScope(typeOf(field), reference); - assertThat(descriptionsIn(scope), containAll("test.proto.Type", ".test.proto.Type", - "test.proto.Address", ".test.proto.Address", - "Contact", "proto.Contact", "google.proto.Contact", - "com.google.proto.Contact", ".com.google.proto.Contact")); + assertThat(descriptionsIn(scope), containAll("test.proto.Type", "test.proto.Address", + "Contact", "proto.Contact", "google.proto.Contact", "com.google.proto.Contact")); + } + + // // Create file sample_proto.proto + // syntax = "proto2"; + // package sample.proto; + // + // enum Fruit { + // GRAPE = 1; + // } + + // syntax = "proto2"; + // package sample.proto.foo; + // import "sample_proto.proto"; + // + // enum Fruit { + // GRAPE = 1; + // } + // + // message Fruits { + // optional Fruit grape = 1; + // } + @Test public void should_provide_nearest_ComplexType() { + 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(); + assertEquals(expectedEnum, scopedEnum); } private static ComplexTypeLink typeOf(MessageField field) {
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 58c3c53..f3fcaa3 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
@@ -13,7 +13,8 @@ import static com.google.eclipse.protobuf.junit.IEObjectDescriptions.descriptionsIn; import static com.google.eclipse.protobuf.junit.core.IntegrationTestModule.integrationTestModule; import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; -import static com.google.eclipse.protobuf.junit.matchers.ContainAllNames.containAll; +import static com.google.eclipse.protobuf.junit.matchers.ContainNames.contain; +import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.FIELD_NAME__TARGET; import org.eclipse.emf.ecore.EReference; import org.eclipse.xtext.scoping.IScope; @@ -21,6 +22,8 @@ import org.junit.Test; import com.google.eclipse.protobuf.junit.core.XtextRule; +import com.google.eclipse.protobuf.protobuf.ComplexValue; +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.ValueField; @@ -34,7 +37,6 @@ public class ProtobufScopeProvider_scope_FieldName_target_with_ExtensionFieldName_Test { @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule()); - @Inject private EReference reference; @Inject private ProtobufScopeProvider scopeProvider; // syntax = "proto2"; @@ -60,9 +62,11 @@ // } // }; @Test public void should_provide_sources_for_aggregate_field() { - ValueField field = xtext.find("google.proto.test.fileopt", "]", ValueField.class); + ComplexValueField valueField = xtext.find("file", " {", ComplexValueField.class); + ComplexValue value = valueField.getValues().get(0); + ValueField field = value.getFields().get(0); ExtensionFieldName name = (ExtensionFieldName) field.getName(); - IScope scope = scopeProvider.scope_FieldName_target(name, reference); - assertThat(descriptionsIn(scope), containAll("google.proto.test.fileopt", ".google.proto.test.fileopt")); + IScope scope = scopeProvider.getScope(name, FIELD_NAME__TARGET); + assertThat(descriptionsIn(scope), contain("google.proto.test.fileopt")); } }
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_FieldName_target_with_NormalFieldName_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_FieldName_target_with_NormalFieldName_Test.java index f52b4c1..538d71a 100644 --- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_FieldName_target_with_NormalFieldName_Test.java +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_FieldName_target_with_NormalFieldName_Test.java
@@ -9,11 +9,12 @@ package com.google.eclipse.protobuf.scoping; import static org.junit.Assert.assertThat; - import static com.google.eclipse.protobuf.junit.IEObjectDescriptions.descriptionsIn; import static com.google.eclipse.protobuf.junit.core.IntegrationTestModule.integrationTestModule; import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; +import static com.google.eclipse.protobuf.junit.matchers.ContainNames.contain; import static com.google.eclipse.protobuf.junit.matchers.ContainAllFieldsInMessage.containAllFieldsIn; +import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.FIELD_NAME__TARGET; import org.eclipse.emf.ecore.EReference; import org.eclipse.xtext.scoping.IScope; @@ -28,7 +29,7 @@ import com.google.inject.Inject; /** - * Tests for <code>{@link ProtobufScopeProvider#scope_FieldName_target(FieldName, EReference)}</code> + * Tests for <code>{@link ProtobufScopeProvider#getScope(FieldName, EReference)}</code> * * @author alruiz@google.com (Alex Ruiz) */ @@ -56,9 +57,8 @@ @Test public void should_provide_sources_for_aggregate_field() { ValueField field = xtext.find("code", ":", ValueField.class); NormalFieldName name = (NormalFieldName) field.getName(); - IScope scope = scopeProvider.scope_FieldName_target(name, reference); - Message message = xtext.find("Type", " {", Message.class); - assertThat(descriptionsIn(scope), containAllFieldsIn(message)); + IScope scope = scopeProvider.getScope(name, FIELD_NAME__TARGET); + assertThat(descriptionsIn(scope), contain("code")); } // syntax = "proto2"; @@ -84,9 +84,9 @@ @Test public void should_provide_sources_for_nested_field_notation_in_option() { ValueField field = xtext.find("value", ":", ValueField.class); NormalFieldName name = (NormalFieldName) field.getName(); - IScope scope = scopeProvider.scope_FieldName_target(name, reference); + IScope scope = scopeProvider.getScope(name, FIELD_NAME__TARGET); Message message = xtext.find("Names", " {", Message.class); - assertThat(descriptionsIn(scope), containAllFieldsIn(message)); + assertThat(descriptionsIn(scope), contain("value")); } // syntax = "proto2"; @@ -107,9 +107,8 @@ @Test public void should_provide_sources_for_field_notation_in_field_option() { ValueField field = xtext.find("code", ":", ValueField.class); NormalFieldName name = (NormalFieldName) field.getName(); - IScope scope = scopeProvider.scope_FieldName_target(name, reference); - Message message = xtext.find("Type", " {", Message.class); - assertThat(descriptionsIn(scope), containAllFieldsIn(message)); + IScope scope = scopeProvider.getScope(name, FIELD_NAME__TARGET); + assertThat(descriptionsIn(scope), contain("code")); } // syntax = "proto2"; @@ -135,8 +134,8 @@ @Test public void should_provide_sources_for_nested_field_notation_in_field_option() { ValueField field = xtext.find("value", ":", ValueField.class); NormalFieldName name = (NormalFieldName) field.getName(); - IScope scope = scopeProvider.scope_FieldName_target(name, reference); + IScope scope = scopeProvider.getScope(name, FIELD_NAME__TARGET); Message message = xtext.find("Names", " {", Message.class); - assertThat(descriptionsIn(scope), containAllFieldsIn(message)); + assertThat(descriptionsIn(scope), contain("value")); } }
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 1c13b6f..aad3151 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
@@ -9,10 +9,12 @@ package com.google.eclipse.protobuf.scoping; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertEquals; import static com.google.eclipse.protobuf.junit.IEObjectDescriptions.descriptionsIn; import static com.google.eclipse.protobuf.junit.core.IntegrationTestModule.integrationTestModule; import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; -import static com.google.eclipse.protobuf.junit.matchers.ContainAllLiteralsInEnum.containAllLiteralsIn; +import static com.google.eclipse.protobuf.junit.matchers.ContainNames.contain; +import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.LITERAL_LINK__TARGET; import org.eclipse.emf.ecore.EReference; import org.eclipse.xtext.scoping.IScope; @@ -20,23 +22,23 @@ import org.junit.Test; import com.google.eclipse.protobuf.junit.core.XtextRule; +import com.google.eclipse.protobuf.protobuf.DefaultValueFieldOption; import com.google.eclipse.protobuf.protobuf.Enum; import com.google.eclipse.protobuf.protobuf.FieldOption; +import com.google.eclipse.protobuf.protobuf.Literal; import com.google.eclipse.protobuf.protobuf.LiteralLink; +import com.google.eclipse.protobuf.protobuf.MessageField; import com.google.eclipse.protobuf.protobuf.Option; -import com.google.eclipse.protobuf.util.EResources; import com.google.inject.Inject; /** - * Tests for <code>{@link ProtobufScopeProvider#scope_LiteralLink_target(LiteralLink, EReference)}</code>. + * Tests for <code>{@link ProtobufScopeProvider#getScope(LiteralLink, EReference)}</code>. * * @author alruiz@google.com (Alex Ruiz) */ public class ProtobufScopeProvider_scope_LiteralLink_target_Test { @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule()); - @Inject private EReference reference; - @Inject private ProtoDescriptorProvider descriptorProvider; @Inject private ProtobufScopeProvider scopeProvider; // syntax = "proto2"; @@ -50,10 +52,10 @@ // optional Type type = 1 [default = ONE]; // } @Test public void should_provide_Literals_for_default_value() { - FieldOption option = xtext.find("default", FieldOption.class); - IScope scope = scopeProvider.scope_LiteralLink_target(valueOf(option), reference); - Enum typeEnum = xtext.find("Type", " {", Enum.class); - assertThat(descriptionsIn(scope), containAllLiteralsIn(typeEnum)); + MessageField field = xtext.find("type", " =", MessageField.class); + FieldOption option = field.getFieldOptions().get(0); + IScope scope = scopeProvider.getScope(valueOf(option), LITERAL_LINK__TARGET); + assertThat(descriptionsIn(scope), contain("ONE", "TWO")); } // syntax = "proto2"; @@ -61,10 +63,8 @@ // option optimize_for = SPEED; @Test public void should_provide_Literals_for_native_option() { Option option = xtext.find("optimize_for", Option.class); - IScope scope = scopeProvider.scope_LiteralLink_target(valueOf(option), reference); - Enum optimizeModeEnum = descriptorProvider.primaryDescriptor( - EResources.getProjectOf(xtext.root().eResource())).enumByName("OptimizeMode"); - assertThat(descriptionsIn(scope), containAllLiteralsIn(optimizeModeEnum)); + IScope scope = scopeProvider.getScope(valueOf(option), LITERAL_LINK__TARGET); + assertThat(descriptionsIn(scope), contain("SPEED", "CODE_SIZE", "LITE_RUNTIME")); } // syntax = "proto2"; @@ -83,9 +83,8 @@ // option (type) = ONE; @Test public void should_provide_Literals_for_source_of_custom_option() { Option option = xtext.find("type", ")", Option.class); - IScope scope = scopeProvider.scope_LiteralLink_target(valueOf(option), reference); - Enum typeEnum = xtext.find("Type", " {", Enum.class); - assertThat(descriptionsIn(scope), containAllLiteralsIn(typeEnum)); + IScope scope = scopeProvider.getScope(valueOf(option), LITERAL_LINK__TARGET); + assertThat(descriptionsIn(scope), contain("ONE", "TWO")); } // syntax = "proto2"; @@ -108,9 +107,9 @@ // option (info).type = ONE; @Test public void should_provide_Literals_for_source_of_field_of_custom_option() { Option option = xtext.find("info", ")", Option.class); - IScope scope = scopeProvider.scope_LiteralLink_target(valueOf(option), reference); - Enum typeEnum = xtext.find("Type", " {", Enum.class); - assertThat(descriptionsIn(scope), containAllLiteralsIn(typeEnum)); + IScope scope = scopeProvider.getScope(valueOf(option), LITERAL_LINK__TARGET); + xtext.find("Type", " {", Enum.class); + assertThat(descriptionsIn(scope), contain("ONE", "TWO")); } private static LiteralLink valueOf(Option option) { @@ -124,11 +123,8 @@ // } @Test public void should_provide_Literals_for_source_of_native_field_option() { FieldOption option = xtext.find("ctype", FieldOption.class); - IScope scope = scopeProvider.scope_LiteralLink_target(valueOf(option), reference); - ProtoDescriptor descriptor = - descriptorProvider.primaryDescriptor(EResources.getProjectOf(xtext.resource())); - Enum cTypeEnum = descriptor.enumByName("CType"); - assertThat(descriptionsIn(scope), containAllLiteralsIn(cTypeEnum)); + IScope scope = scopeProvider.getScope(valueOf(option), LITERAL_LINK__TARGET); + assertThat(descriptionsIn(scope), contain("STRING", "CORD", "STRING_PIECE")); } // syntax = "proto2"; @@ -149,9 +145,9 @@ // } @Test public void should_provide_Literals_for_source_of_custom_field_option() { FieldOption option = xtext.find("type", ")", FieldOption.class); - IScope scope = scopeProvider.scope_LiteralLink_target(valueOf(option), reference); - Enum typeEnum = xtext.find("Type", " {", Enum.class); - assertThat(descriptionsIn(scope), containAllLiteralsIn(typeEnum)); + IScope scope = scopeProvider.getScope(valueOf(option), LITERAL_LINK__TARGET); + xtext.find("Type", " {", Enum.class); + assertThat(descriptionsIn(scope), contain("ONE", "TWO")); } // syntax = "proto2"; @@ -176,9 +172,43 @@ // } @Test public void should_provide_Literals_for_source_of_field_in_custom_field_option() { FieldOption option = xtext.find("info", ")", FieldOption.class); - IScope scope = scopeProvider.scope_LiteralLink_target(valueOf(option), reference); - Enum typeEnum = xtext.find("Type", " {", Enum.class); - assertThat(descriptionsIn(scope), containAllLiteralsIn(typeEnum)); + IScope scope = scopeProvider.getScope(valueOf(option), LITERAL_LINK__TARGET); + xtext.find("Type", " {", Enum.class); + assertThat(descriptionsIn(scope), contain("ONE", "TWO")); + } + + // // Create file sample_proto.proto + // syntax = "proto2"; + // package sample.proto; + // + // message Example { + // enum Alpha { + // BAR = 1; + // } + // } + + // syntax = "proto2"; + // package example.proto; + // import "sample_proto.proto"; + // + // message Fruits { + // enum Beta { + // BAR = 1; + // } + // optional Grape grape = 4 [default = BAR]; + // } + @Test public void should_provide_Literal_nearest_to_option() { + // Beta + MessageField field = xtext.find("grape", " =", MessageField.class); + DefaultValueFieldOption option = (DefaultValueFieldOption) field.getFieldOptions().get(0); + LiteralLink link = (LiteralLink) option.getValue(); + Enum enumBeta = (Enum)link.getTarget().eContainer(); + + // Alpha + Enum beta = xtext.find("Beta", Enum.class); + Literal literal = (Literal) beta.getElements().get(0); + Enum expectedEnum = (Enum) literal.eContainer(); + assertEquals(expectedEnum, enumBeta); } private static LiteralLink valueOf(FieldOption option) {
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionField_target_with_ExtensionOptionField_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionField_target_with_ExtensionOptionField_Test.java index d883338..4dc1113 100644 --- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionField_target_with_ExtensionOptionField_Test.java +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionField_target_with_ExtensionOptionField_Test.java
@@ -9,11 +9,11 @@ package com.google.eclipse.protobuf.scoping; import static org.junit.Assert.assertThat; - import static com.google.eclipse.protobuf.junit.IEObjectDescriptions.descriptionsIn; import static com.google.eclipse.protobuf.junit.core.IntegrationTestModule.integrationTestModule; import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; -import static com.google.eclipse.protobuf.junit.matchers.ContainAllNames.containAll; +import static com.google.eclipse.protobuf.junit.matchers.ContainNames.contain; +import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.OPTION_SOURCE__TARGET; import org.eclipse.emf.ecore.EReference; import org.eclipse.xtext.scoping.IScope; @@ -28,14 +28,13 @@ import com.google.inject.Inject; /** - * Tests for <code>{@link ProtobufScopeProvider#scope_OptionField_target(OptionField, EReference)}</code>. + * Tests for <code>{@link ProtobufScopeProvider#getScope(OptionField, EReference)}</code>. * * @author alruiz@google.com (Alex Ruiz) */ public class ProtobufScopeProvider_scope_OptionField_target_with_ExtensionOptionField_Test { @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule()); - @Inject private EReference reference; @Inject private ProtobufScopeProvider scopeProvider; // syntax = "proto2"; @@ -62,8 +61,8 @@ @Test public void should_provide_extend_message_fields_for_first_field_in_custom_option() { CustomOption option = xtext.find("type", ")", CustomOption.class); ExtensionOptionField codeOptionField = (ExtensionOptionField) option.getFields().get(0); - IScope scope = scopeProvider.scope_OptionField_target(codeOptionField, reference); - assertThat(descriptionsIn(scope), containAll("active", "com.google.proto.active", ".com.google.proto.active")); + IScope scope = scopeProvider.getScope(codeOptionField, OPTION_SOURCE__TARGET); + assertThat(descriptionsIn(scope), contain("active", "com.google.proto.active")); } // syntax = "proto2"; @@ -92,7 +91,7 @@ @Test public void should_provide_message_fields_for_first_field_in_field_custom_option() { CustomFieldOption option = xtext.find("type", ")", CustomFieldOption.class); ExtensionOptionField codeOptionField = (ExtensionOptionField) option.getFields().get(0); - IScope scope = scopeProvider.scope_OptionField_target(codeOptionField, reference); - assertThat(descriptionsIn(scope), containAll("active", "com.google.proto.active", ".com.google.proto.active")); + IScope scope = scopeProvider.getScope(codeOptionField, OPTION_SOURCE__TARGET); + assertThat(descriptionsIn(scope), contain("active", "com.google.proto.active")); } }
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionField_target_with_MessageOptionField_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionField_target_with_MessageOptionField_Test.java index f46338f..ee5ad96 100644 --- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionField_target_with_MessageOptionField_Test.java +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionField_target_with_MessageOptionField_Test.java
@@ -9,14 +9,14 @@ package com.google.eclipse.protobuf.scoping; import static org.junit.Assert.assertThat; - import static com.google.eclipse.protobuf.junit.IEObjectDescriptions.descriptionsIn; import static com.google.eclipse.protobuf.junit.core.IntegrationTestModule.integrationTestModule; import static com.google.eclipse.protobuf.junit.core.SearchOption.IGNORE_CASE; import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; -import static com.google.eclipse.protobuf.junit.matchers.ContainAllFieldsInMessage.containAllFieldsIn; -import static com.google.eclipse.protobuf.junit.matchers.ContainAllNames.containAll; +import static com.google.eclipse.protobuf.junit.matchers.ContainNames.contain; +import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.OPTION_FIELD__TARGET; +import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EReference; import org.eclipse.xtext.scoping.IScope; import org.junit.Rule; @@ -25,21 +25,20 @@ import com.google.eclipse.protobuf.junit.core.XtextRule; import com.google.eclipse.protobuf.protobuf.CustomFieldOption; import com.google.eclipse.protobuf.protobuf.CustomOption; -import com.google.eclipse.protobuf.protobuf.Group; -import com.google.eclipse.protobuf.protobuf.Message; +import com.google.eclipse.protobuf.protobuf.FieldOption; +import com.google.eclipse.protobuf.protobuf.MessageField; import com.google.eclipse.protobuf.protobuf.MessageOptionField; import com.google.eclipse.protobuf.protobuf.OptionField; import com.google.inject.Inject; /** - * Tests for <code>{@link ProtobufScopeProvider#scope_OptionField_target(OptionField, EReference)}</code>. + * Tests for <code>{@link ProtobufScopeProvider#getScope(OptionField, EReference)}</code>. * * @author alruiz@google.com (Alex Ruiz) */ public class ProtobufScopeProvider_scope_OptionField_target_with_MessageOptionField_Test { @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule()); - @Inject private EReference reference; @Inject private ProtobufScopeProvider scopeProvider; // syntax = "proto2"; @@ -59,9 +58,8 @@ @Test public void should_provide_message_fields_for_first_field_in_custom_option() { CustomOption option = xtext.find("type", ")", CustomOption.class); MessageOptionField codeOptionField = (MessageOptionField) option.getFields().get(0); - IScope scope = scopeProvider.scope_OptionField_target(codeOptionField, reference); - Message typeMessage = xtext.find("Type", " {", Message.class); - assertThat(descriptionsIn(scope), containAllFieldsIn(typeMessage)); + IScope scope = scopeProvider.getScope(codeOptionField, OPTION_FIELD__TARGET); + assertThat(descriptionsIn(scope), contain("code", "name")); } // syntax = "proto2"; @@ -84,10 +82,9 @@ // option (type).code.number = 68; @Test public void should_provide_message_fields_for_field_in_custom_option() { CustomOption option = xtext.find("type", ")", CustomOption.class); - MessageOptionField numberOptionField = (MessageOptionField) option.getFields().get(1); - IScope scope = scopeProvider.scope_OptionField_target(numberOptionField, reference); - Message codeMessage = xtext.find("Code", " {", Message.class); - assertThat(descriptionsIn(scope), containAllFieldsIn(codeMessage)); + OptionField numberOptionField = (MessageOptionField) option.getFields().get(1); + IScope scope = scopeProvider.getScope(numberOptionField, OPTION_FIELD__TARGET); + assertThat(descriptionsIn(scope), contain("number")); } // syntax = "proto2"; @@ -105,9 +102,8 @@ @Test public void should_provide_group_fields_for_first_field_in_custom_option() { CustomOption option = xtext.find("type", ")", CustomOption.class, IGNORE_CASE); MessageOptionField codeOptionField = (MessageOptionField) option.getFields().get(0); - IScope scope = scopeProvider.scope_OptionField_target(codeOptionField, reference); - Group groupMessage = xtext.find("Type", " =", Group.class); - assertThat(descriptionsIn(scope), containAllFieldsIn(groupMessage)); + IScope scope = scopeProvider.getScope(codeOptionField, OPTION_FIELD__TARGET); + assertThat(descriptionsIn(scope), contain("code", "name")); } // syntax = "proto2"; @@ -129,9 +125,8 @@ @Test public void should_provide_message_fields_for_first_field_in_field_custom_option() { CustomFieldOption option = xtext.find("type", ")", CustomFieldOption.class); MessageOptionField codeOptionField = (MessageOptionField) option.getFields().get(0); - IScope scope = scopeProvider.scope_OptionField_target(codeOptionField, reference); - Message typeMessage = xtext.find("Type", " {", Message.class); - assertThat(descriptionsIn(scope), containAllFieldsIn(typeMessage)); + IScope scope = scopeProvider.getScope(codeOptionField, OPTION_FIELD__TARGET); + assertThat(descriptionsIn(scope), contain("code", "name")); } // syntax = "proto2"; @@ -157,9 +152,8 @@ @Test public void should_provide_message_fields_for_field_in_field_custom_option() { CustomFieldOption option = xtext.find("type", ")", CustomFieldOption.class); MessageOptionField numberOptionField = (MessageOptionField) option.getFields().get(1); - IScope scope = scopeProvider.scope_OptionField_target(numberOptionField, reference); - Message codeMessage = xtext.find("Code", " {", Message.class); - assertThat(descriptionsIn(scope), containAllFieldsIn(codeMessage)); + IScope scope = scopeProvider.getScope(numberOptionField, OPTION_FIELD__TARGET); + assertThat(descriptionsIn(scope), contain("number")); } // syntax = "proto2"; @@ -177,11 +171,12 @@ // optional boolean active = 1 [(type).code = 68]; // } @Test public void should_provide_group_fields_for_first_field_in_field_custom_option() { - CustomFieldOption option = xtext.find("type", ")", CustomFieldOption.class, IGNORE_CASE); - MessageOptionField codeOptionField = (MessageOptionField) option.getFields().get(0); - IScope scope = scopeProvider.scope_OptionField_target(codeOptionField, reference); - Group groupMessage = xtext.find("Type", " =", Group.class); - assertThat(descriptionsIn(scope), containAllFieldsIn(groupMessage)); + MessageField messageField = xtext.find("active", MessageField.class); + EList<FieldOption> fieldOptions = messageField.getFieldOptions(); + CustomFieldOption customFieldOption = (CustomFieldOption) fieldOptions.get(0); + OptionField codeOptionField = customFieldOption.getFields().get(0); + IScope scope = scopeProvider.getScope(codeOptionField, OPTION_FIELD__TARGET); + assertThat(descriptionsIn(scope), contain("code", "name")); } // syntax = "proto2"; @@ -208,7 +203,39 @@ @Test public void should_provide_field_option_from_oneof() { CustomFieldOption option = xtext.find("type", ")", CustomFieldOption.class); MessageOptionField codeOptionField = (MessageOptionField) option.getFields().get(0); - IScope scope = scopeProvider.scope_OptionField_target(codeOptionField, reference); - assertThat(descriptionsIn(scope), containAll("foo", "bar", "baz")); + IScope scope = scopeProvider.getScope(codeOptionField, OPTION_FIELD__TARGET); + assertThat(descriptionsIn(scope), contain("foo", "bar", "baz")); + } + + // syntax = "proto2"; + // + // package com.google.proto; + // + // import 'google/protobuf/descriptor.proto'; + // + // message Type { + // bool foo = 1; + // oneof choose_one { + // bool bar = 2; + // bool baz = 3; + // } + // oneof choose_another { + // bool qux = 4; + // bool quux = 5; + // } + // } + // + // extend google.protobuf.FieldOptions { + // optional Type type = 1000; + // } + // + // message Person { + // optional bool active = 1 [(type).bar = true]; + // } + @Test public void should_provide_field_option_from_multiple_oneof() { + CustomFieldOption option = xtext.find("type", ")", CustomFieldOption.class); + MessageOptionField codeOptionField = (MessageOptionField) option.getFields().get(0); + IScope scope = scopeProvider.getScope(codeOptionField, OPTION_FIELD__TARGET); + assertThat(descriptionsIn(scope), contain("foo", "bar", "baz", "qux", "quux")); } }
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionSource_target_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionSource_target_Test.java index 8b30f48..175a43a 100644 --- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionSource_target_Test.java +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionSource_target_Test.java
@@ -12,37 +12,27 @@ import static com.google.eclipse.protobuf.junit.IEObjectDescriptions.descriptionsIn; import static com.google.eclipse.protobuf.junit.core.IntegrationTestModule.integrationTestModule; import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; -import static com.google.eclipse.protobuf.junit.matchers.ContainAllFields.containAll; -import static com.google.eclipse.protobuf.junit.matchers.ContainAllNames.containAll; -import static com.google.eclipse.protobuf.scoping.OptionType.FIELD; -import static com.google.eclipse.protobuf.scoping.OptionType.FILE; - -import java.util.Collection; - +import static com.google.eclipse.protobuf.junit.matchers.ContainNames.contain; +import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.OPTION_SOURCE__TARGET; import org.eclipse.emf.ecore.EReference; import org.eclipse.xtext.scoping.IScope; import org.junit.Rule; import org.junit.Test; - import com.google.eclipse.protobuf.junit.core.XtextRule; import com.google.eclipse.protobuf.protobuf.CustomFieldOption; -import com.google.eclipse.protobuf.protobuf.MessageField; import com.google.eclipse.protobuf.protobuf.NativeFieldOption; import com.google.eclipse.protobuf.protobuf.Option; import com.google.eclipse.protobuf.protobuf.OptionSource; -import com.google.eclipse.protobuf.util.EResources; import com.google.inject.Inject; /** - * Tests for <code>{@link ProtobufScopeProvider#scope_OptionSource_target(OptionSource, EReference)}</code> + * Tests for <code>{@link ProtobufScopeProvider#getScope(OptionSource, EReference)}</code> * * @author alruiz@google.com (Alex Ruiz) */ public class ProtobufScopeProvider_scope_OptionSource_target_Test { @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule()); - @Inject private EReference reference; - @Inject private ProtoDescriptorProvider descriptorProvider; @Inject private ProtobufScopeProvider scopeProvider; // syntax = "proto2"; @@ -50,11 +40,10 @@ // option optimize_for = SPEED; @Test public void should_provide_sources_for_native_option() { Option option = xtext.find("optimize_for", Option.class); - IScope scope = scopeProvider.scope_OptionSource_target(option.getSource(), reference); - ProtoDescriptor descriptor = - descriptorProvider.primaryDescriptor(EResources.getProjectOf(xtext.resource())); - Collection<MessageField> optionSources = descriptor.optionsOfType(FILE); - assertThat(descriptionsIn(scope), containAll(optionSources)); + IScope scope = scopeProvider.getScope(option.getSource(), OPTION_SOURCE__TARGET); + assertThat(descriptionsIn(scope), contain("java_package", "java_outer_classname", + "java_multiple_files", "java_generate_equals_and_hash", "optimize_for", + "cc_generic_services", "java_generic_services", "py_generic_services")); } // syntax = "proto2"; @@ -64,11 +53,9 @@ // } @Test public void should_provide_sources_for_native_field_option() { NativeFieldOption option = xtext.find("ctype", NativeFieldOption.class); - IScope scope = scopeProvider.scope_OptionSource_target(option.getSource(), reference); - ProtoDescriptor descriptor = - descriptorProvider.primaryDescriptor(EResources.getProjectOf(xtext.resource())); - Collection<MessageField> optionSources = descriptor.optionsOfType(FIELD); - assertThat(descriptionsIn(scope), containAll(optionSources)); + IScope scope = scopeProvider.getScope(option.getSource(), OPTION_SOURCE__TARGET); + assertThat(descriptionsIn(scope), contain("ctype", "packed", "deprecated", + "experimental_map_key")); } // syntax = "proto2"; @@ -84,11 +71,10 @@ // option (code) = 68; @Test public void should_provide_sources_for_custom_option() { Option option = xtext.find("code", ")", Option.class); - IScope scope = scopeProvider.scope_OptionSource_target(option.getSource(), reference); - assertThat(descriptionsIn(scope), containAll("code", "proto.code", "google.proto.code", "com.google.proto.code", - ".com.google.proto.code", - "info", "proto.info", "google.proto.info", "com.google.proto.info", - ".com.google.proto.info")); + IScope scope = scopeProvider.getScope(option.getSource(), OPTION_SOURCE__TARGET); + assertThat(descriptionsIn(scope), contain("code", "proto.code", "google.proto.code", + "com.google.proto.code", "info", "proto.info", "google.proto.info", + "com.google.proto.info")); } // // Create file custom-options.proto @@ -110,9 +96,8 @@ // option (test.proto.code) = 68; @Test public void should_provide_imported_sources_for_custom_option() { Option option = xtext.find("code", ")", Option.class); - IScope scope = scopeProvider.scope_OptionSource_target(option.getSource(), reference); - assertThat(descriptionsIn(scope), containAll("test.proto.code", ".test.proto.code", - "test.proto.info", ".test.proto.info")); + IScope scope = scopeProvider.getScope(option.getSource(), OPTION_SOURCE__TARGET); + assertThat(descriptionsIn(scope), contain("test.proto.code", "test.proto.info")); } // // Create file custom-options.proto @@ -134,11 +119,10 @@ // option (code) = 68; @Test public void should_provide_imported_sources_for_custom_option_with_equal_package() { Option option = xtext.find("code", ")", Option.class); - IScope scope = scopeProvider.scope_OptionSource_target(option.getSource(), reference); - assertThat(descriptionsIn(scope), containAll("code", "proto.code", "google.proto.code", "com.google.proto.code", - ".com.google.proto.code", - "info", "proto.info", "google.proto.info", "com.google.proto.info", - ".com.google.proto.info")); + IScope scope = scopeProvider.getScope(option.getSource(), OPTION_SOURCE__TARGET); + assertThat(descriptionsIn(scope), contain("code", "proto.code", "google.proto.code", + "com.google.proto.code", "info", "proto.info", "google.proto.info", + "com.google.proto.info")); } // syntax = "proto2"; @@ -155,10 +139,9 @@ // } @Test public void should_provide_sources_for_custom_field_option() { CustomFieldOption option = xtext.find("code", ")", CustomFieldOption.class); - IScope scope = scopeProvider.scope_OptionSource_target(option.getSource(), reference); - assertThat(descriptionsIn(scope), containAll("code", "proto.code", "google.proto.code", "com.google.proto.code", - ".com.google.proto.code", - "info", "proto.info", "google.proto.info", "com.google.proto.info", - ".com.google.proto.info")); + IScope scope = scopeProvider.getScope(option.getSource(), OPTION_SOURCE__TARGET); + assertThat(descriptionsIn(scope), contain("code", "proto.code", "google.proto.code", + "com.google.proto.code", "info", "proto.info", "google.proto.info", + "com.google.proto.info")); } }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/ProtobufRuntimeModule.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/ProtobufRuntimeModule.java index 378fe86..d793075 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/ProtobufRuntimeModule.java +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/ProtobufRuntimeModule.java
@@ -15,6 +15,7 @@ import com.google.eclipse.protobuf.resource.FastXtextResourceSet; import com.google.eclipse.protobuf.resource.GlobalResourceServiceProvider; import com.google.eclipse.protobuf.scoping.ExtensionRegistryProvider; +import com.google.eclipse.protobuf.scoping.ProtobufCaseInsensitivityHelper; import com.google.eclipse.protobuf.validation.ProtobufResourceValidator; import com.google.eclipse.protobuf.validation.ProtobufSyntaxErrorMessageProvider; import com.google.inject.Binder; @@ -27,6 +28,7 @@ import org.eclipse.xtext.resource.IGlobalServiceProvider; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.resource.XtextResourceSet; +import org.eclipse.xtext.scoping.ICaseInsensitivityHelper; import org.eclipse.xtext.validation.IResourceValidator; /** @@ -42,6 +44,10 @@ return ProtobufQualifiedNameConverter.class; } + public Class<? extends ICaseInsensitivityHelper> bindICaseInsensitivityHelper() { + return ProtobufCaseInsensitivityHelper.class; + } + @Override public Class<? extends IQualifiedNameProvider> bindIQualifiedNameProvider() { return ProtobufQualifiedNameProvider.class;
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/QualifiedNames.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/QualifiedNames.java index a7402ad..b7708de 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/QualifiedNames.java +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/QualifiedNames.java
@@ -8,8 +8,7 @@ */ package com.google.eclipse.protobuf.model.util; -import static com.google.common.collect.Lists.newArrayList; - +import java.util.ArrayList; import java.util.List; import org.eclipse.xtext.naming.QualifiedName; @@ -23,14 +22,23 @@ * * @author alruiz@google.com (Alex Ruiz) */ -@Singleton public class QualifiedNames { +@Singleton +public class QualifiedNames { @Inject private StringLists stringLists; + public static QualifiedName removeLeadingDot(QualifiedName name) { + String firstSegment = name.getFirstSegment(); + if (firstSegment != null && firstSegment.isEmpty()) { + return name.skipFirst(1); + } + return name; + } + public QualifiedName addLeadingDot(QualifiedName name) { - if (name.getFirstSegment().equals("")) { + if (name.getFirstSegment().isEmpty()) { return name; } - List<String> segments = newArrayList(); + List<String> segments = new ArrayList<>(); segments.addAll(name.getSegments()); segments.add(0, ""); return createQualifiedName(segments);
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufCaseInsensitivityHelper.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufCaseInsensitivityHelper.java new file mode 100644 index 0000000..34027fd --- /dev/null +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufCaseInsensitivityHelper.java
@@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016 Google Inc. + * + * All rights reserved. This program and the accompanying materials are made + * available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package com.google.eclipse.protobuf.scoping; + +import org.eclipse.emf.ecore.EReference; +import org.eclipse.xtext.scoping.impl.CaseInsensitivityHelper; + +import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.OPTION_SOURCE__TARGET; + +/** + * Specify cases where scoping is case insensitive. + * + * @author atrookey@google.com (Alexander Rookey) + */ +public class ProtobufCaseInsensitivityHelper extends CaseInsensitivityHelper { + @Override + public boolean isIgnoreCase(EReference reference) { + if (reference.equals(OPTION_SOURCE__TARGET)) { + return true; + } + return super.isIgnoreCase(reference); + } +}
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 fcc0476..15f9a2c 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
@@ -8,7 +8,7 @@ */ package com.google.eclipse.protobuf.scoping; -import static java.util.Collections.emptyList; +import static com.google.eclipse.protobuf.model.util.QualifiedNames.removeLeadingDot; import java.util.ArrayList; import java.util.List; @@ -22,10 +22,18 @@ import org.eclipse.xtext.scoping.impl.ImportNormalizer; import org.eclipse.xtext.scoping.impl.ImportScope; -import com.google.common.collect.Lists; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; +/** + * {@link ImportScope} that allows additional ImportNormalizers to be added after initialization. + * + * @author (atrookey@google.com) Alexander Rookey + */ public class ProtobufImportScope extends ImportScope { private final EClass type; + private List<ImportNormalizer> normalizers; public ProtobufImportScope( List<ImportNormalizer> namespaceResolvers, @@ -35,46 +43,74 @@ boolean ignoreCase) { super(namespaceResolvers, parent, importFrom, type, ignoreCase); this.type = type; + this.normalizers = removeDuplicates(namespaceResolvers); } @Override protected Iterable<IEObjectDescription> getAliasedElements( Iterable<IEObjectDescription> candidates) { - ArrayList<IEObjectDescription> descriptions = - Lists.newArrayList(super.getAliasedElements(candidates)); + Multimap<QualifiedName, IEObjectDescription> keyToDescription = LinkedHashMultimap.create(); + Multimap<QualifiedName, ImportNormalizer> keyToNormalizer = HashMultimap.create(); + for (IEObjectDescription imported : candidates) { - descriptions.add(new AliasedEObjectDescription(addLeadingDot(imported.getName()), imported)); + QualifiedName fullyQualifiedName = imported.getName(); + for (ImportNormalizer normalizer : normalizers) { + QualifiedName alias = normalizer.deresolve(fullyQualifiedName); + if (alias != null) { + QualifiedName key = alias; + if (isIgnoreCase()) { + key = key.toLowerCase(); + } + keyToDescription.put(key, new AliasedEObjectDescription(alias, imported)); + keyToNormalizer.put(key, normalizer); + } + } } - return descriptions; + for (QualifiedName name : keyToNormalizer.keySet()) { + if (keyToNormalizer.get(name).size() > 1) keyToDescription.removeAll(name); + } + return keyToDescription.values(); } + // TODO (atrookey) Refactor this method for clarity @Override protected Iterable<IEObjectDescription> getLocalElementsByName(QualifiedName name) { - List<IEObjectDescription> result = - (List<IEObjectDescription>) super.getLocalElementsByName(name); + List<IEObjectDescription> result = new ArrayList<>(); QualifiedName resolvedQualifiedName = null; - final QualifiedName resolvedName = name.skipFirst(1); ISelectable importFrom = getImportFrom(); - if (resolvedName != null) { - Iterable<IEObjectDescription> resolvedElements = - importFrom.getExportedObjects(type, resolvedName, isIgnoreCase()); - for (IEObjectDescription resolvedElement : resolvedElements) { - if (resolvedQualifiedName == null) resolvedQualifiedName = resolvedName; - else if (!resolvedQualifiedName.equals(resolvedName)) { - if (result.get(0).getEObjectOrProxy() != resolvedElement.getEObjectOrProxy()) { - return emptyList(); + for (ImportNormalizer normalizer : normalizers) { + final QualifiedName resolvedName = normalizer.resolve(name); + if (resolvedName != null) { + Iterable<IEObjectDescription> resolvedElements = + importFrom.getExportedObjects(type, resolvedName, isIgnoreCase()); + for (IEObjectDescription resolvedElement : resolvedElements) { + if (resolvedQualifiedName == null) { + resolvedQualifiedName = resolvedName; + } else if (!resolvedQualifiedName.equals(resolvedName)) { + if (result.get(0).getEObjectOrProxy() != resolvedElement.getEObjectOrProxy()) { + continue; + } } + QualifiedName alias = normalizer.deresolve(resolvedElement.getName()); + if (alias == null) + throw new IllegalStateException( + "Couldn't deresolve " + resolvedElement.getName() + " with import " + normalizer); + final AliasedEObjectDescription aliasedEObjectDescription = + new AliasedEObjectDescription(alias, resolvedElement); + result.add(aliasedEObjectDescription); } - QualifiedName alias = addLeadingDot(resolvedElement.getName()); - final AliasedEObjectDescription aliasedEObjectDescription = - new AliasedEObjectDescription(alias, resolvedElement); - result.add(aliasedEObjectDescription); } } return result; } - private QualifiedName addLeadingDot(QualifiedName qualifiedName) { - return QualifiedName.create("").append(qualifiedName); + /** Before getting element, check for and remove leading dot. */ + @Override + public IEObjectDescription getSingleElement(QualifiedName name) { + return super.getSingleElement(removeLeadingDot(name)); + } + + public void addNormalizer(ImportNormalizer normalizer) { + normalizers.add(normalizer); } }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportUriGlobalScopeProvider.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportUriGlobalScopeProvider.java index df27228..0edb1a1 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportUriGlobalScopeProvider.java +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportUriGlobalScopeProvider.java
@@ -8,6 +8,7 @@ */ package com.google.eclipse.protobuf.scoping; +import com.google.common.base.Predicate; import com.google.eclipse.protobuf.model.util.Imports; import com.google.eclipse.protobuf.model.util.Protobufs; import com.google.eclipse.protobuf.model.util.Resources; @@ -19,7 +20,12 @@ import org.eclipse.core.resources.IProject; import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.resource.IResourceDescription; +import org.eclipse.xtext.resource.IResourceDescriptions; +import org.eclipse.xtext.scoping.IScope; import org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider; import org.eclipse.xtext.util.IResourceScopeCache; @@ -30,6 +36,18 @@ * caches the result. */ public class ProtobufImportUriGlobalScopeProvider extends ImportUriGlobalScopeProvider { + @Override + protected IScope createLazyResourceScope( + IScope parent, + URI uri, + IResourceDescriptions descriptions, + EClass type, + Predicate<IEObjectDescription> filter, + boolean ignoreCase) { + IResourceDescription description = descriptions.getResourceDescription(uri); + return ProtobufSelectableBasedScope.createScope(parent, description, filter, type, ignoreCase); + } + @Inject private Protobufs protobufs; @Inject private Resources resources; @Inject private Imports imports;
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 76cd4ab..9767067 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,6 +8,8 @@ */ 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; @@ -15,6 +17,8 @@ import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.naming.QualifiedName; import org.eclipse.xtext.resource.ISelectable; import org.eclipse.xtext.scoping.IScope; @@ -26,10 +30,18 @@ import com.google.eclipse.protobuf.naming.ProtobufQualifiedNameConverter; import com.google.inject.Inject; +/** + * A local scope provider for the Protobuf language that + * understands namespace imports. + * + * @author (atrookey@google.com) Alexander Rookey + */ public class ProtobufImportedNamespaceAwareLocalScopeProvider extends ImportedNamespaceAwareLocalScopeProvider { @Inject private ProtobufQualifiedNameConverter qualifiedNameConverter; + private static final boolean WILDCARD = true; + @Override protected ImportScope createImportScope( IScope parent, @@ -46,22 +58,22 @@ List<ImportNormalizer> importedNamespaceResolvers = new ArrayList<>(); EList<EObject> eContents = context.eContents(); for (EObject child : eContents) { - String namespace = getImportedNamespace(child); - if (namespace != null) { - ImportNormalizer resolver = createImportedNamespaceResolver(namespace, ignoreCase); + String name = getImportedNamespace(child); + if (name != null && !name.isEmpty()) { + ImportNormalizer resolver = createImportedNamespaceResolver(name, ignoreCase); if (resolver != null) { importedNamespaceResolvers.add(resolver); } - importedNamespaceResolvers.addAll(createResolversForInnerNamespaces(namespace, ignoreCase)); + importedNamespaceResolvers.addAll(createResolversForInnerNamespaces(name, ignoreCase)); } } return importedNamespaceResolvers; } /** - * Creates resolvers required for scoping to handle intersecting packages. The - * imported namespace {@code com.google.proto.foo} requires the following - * resolvers: + * Creates resolvers required for scoping to handle intersecting packages. The imported namespace + * {@code com.google.proto.foo} requires the following resolvers: + * * <ul> * <li>{@code com.*} * <li>{@code com.google.*} @@ -88,14 +100,7 @@ return importedNamespaceResolvers; } - /** - * Creates a new {@link ImportNormalizer} for the given namespace. - * - * @param namespace the namespace. - * @param ignoreCase {@code true} if the resolver should be case insensitive. - * @return a new {@link ImportNormalizer} or {@code null} if the namespace - * cannot be converted to a valid qualified name. - */ + /** Creates an {@link ImportNormalizer} with wildcards. */ @Override protected ImportNormalizer createImportedNamespaceResolver(String namespace, boolean ignoreCase) { if (Strings.isEmpty(namespace)) { @@ -105,6 +110,84 @@ if (importedNamespace == null || importedNamespace.isEmpty()) { return null; } - return doCreateImportNormalizer(importedNamespace, true, ignoreCase); + return doCreateImportNormalizer(importedNamespace, WILDCARD, ignoreCase); + } + + /** + * Creates a {@link ProtobufImportScope} regardless of whether or not + * {@code namespaceResolvers} is empty. + */ + @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; + } + + /** + * 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} + */ + @Override + protected IScope getResourceScope(Resource res, EReference reference) { + return super.getResourceScope(res, reference); + } + + /** Returns a {@link ProtobufSelectableBasedScope} instead of {@link SelectableBasedScope} */ + @Override + protected IScope getResourceScope(IScope parent, EObject context, EReference reference) { + if (context.eResource() == null) { + return parent; + } + ISelectable allDescriptions = getAllDescriptions(context.eResource()); + return ProtobufSelectableBasedScope.createScope( + parent, allDescriptions, reference.getEReferenceType(), isIgnoreCase(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 f34a85a..d541014 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,80 +8,209 @@ */ package com.google.eclipse.protobuf.scoping; +import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.OPTION_SOURCE__TARGET; import static com.google.eclipse.protobuf.scoping.OptionType.typeOf; +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; import static java.util.Collections.emptySet; +import static java.util.Collections.singletonMap; +import static org.eclipse.emf.ecore.resource.ContentHandler.UNSPECIFIED_CONTENT_TYPE; +import static org.eclipse.xtext.EcoreUtil2.resolveLazyCrossReferences; +import static org.eclipse.xtext.resource.XtextResource.OPTION_ENCODING; +import static org.eclipse.xtext.util.CancelIndicator.NullImpl; -import com.google.eclipse.protobuf.model.util.MessageFields; import com.google.eclipse.protobuf.model.util.ModelObjects; -import com.google.eclipse.protobuf.model.util.Options; +import com.google.eclipse.protobuf.naming.NameResolver; +import com.google.eclipse.protobuf.naming.ProtobufQualifiedNameConverter; +import com.google.eclipse.protobuf.preferences.general.PreferenceNames; import com.google.eclipse.protobuf.protobuf.AbstractCustomOption; import com.google.eclipse.protobuf.protobuf.AbstractOption; import com.google.eclipse.protobuf.protobuf.ComplexType; +import com.google.eclipse.protobuf.protobuf.ComplexTypeLink; import com.google.eclipse.protobuf.protobuf.ComplexValue; -import com.google.eclipse.protobuf.protobuf.DefaultValueFieldOption; +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.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.GroupElement; -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; import com.google.eclipse.protobuf.protobuf.MessageField; -import com.google.eclipse.protobuf.protobuf.MessageLink; -import com.google.eclipse.protobuf.protobuf.MessageOptionField; -import com.google.eclipse.protobuf.protobuf.NormalFieldName; +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.OptionField; import com.google.eclipse.protobuf.protobuf.OptionSource; import com.google.eclipse.protobuf.protobuf.Protobuf; import com.google.eclipse.protobuf.protobuf.Rpc; -import com.google.eclipse.protobuf.protobuf.SimpleValueField; import com.google.eclipse.protobuf.protobuf.Stream; import com.google.eclipse.protobuf.protobuf.TypeExtension; +import com.google.eclipse.protobuf.protobuf.TypeLink; +import com.google.eclipse.protobuf.protobuf.ValueField; import com.google.eclipse.protobuf.util.EResources; import com.google.inject.Inject; +import com.google.inject.Provider; +import org.apache.log4j.Level; import org.eclipse.core.resources.IProject; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.resource.Resource; +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.resource.ISelectable; import org.eclipse.xtext.scoping.IScope; import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; -import org.eclipse.xtext.scoping.impl.SimpleScope; +import org.eclipse.xtext.scoping.impl.ImportNormalizer; +import org.eclipse.xtext.scoping.impl.SelectableBasedScope; +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.Collection; -import java.util.Collections; -import java.util.Set; +import java.util.List; /** - * Custom scoping description. + * A scope provider for the Protobuf language. * - * @author alruiz@google.com (Alex Ruiz) - * - * @see <a href="http://www.eclipse.org/Xtext/documentation/latest/xtext.html#scoping">Xtext - * Scoping</a> + * @author atrookey@google.com (Alexander Rookey) */ public class ProtobufScopeProvider extends AbstractDeclarativeScopeProvider implements ScopeProvider { - private static final boolean DO_NOT_IGNORE_CASE = false; - @Inject private ComplexTypeFinderStrategy complexTypeFinderDelegate; @Inject private CustomOptionFieldFinder customOptionFieldFinder; @Inject private CustomOptionFieldNameFinder customOptionFieldNameFinder; @Inject private CustomOptionFinderStrategy customOptionFinderDelegate; - @Inject private ExtensionFieldNameFinderStrategy extensionFieldNameFinderDelegate; @Inject private ExtensionFieldFinderStrategy extensionFieldFinderDelegate; - @Inject private ProtoDescriptorProvider descriptorProvider; + @Inject private ExtensionFieldNameFinderStrategy extensionFieldNameFinderDelegate; @Inject private MessageFieldFinderStrategy messageFieldFinderDelegate; - @Inject private MessageFields messageFields; @Inject private ModelElementFinder modelElementFinder; @Inject private ModelObjects modelObjects; @Inject private NormalFieldNameFinderStrategy normalFieldNameFinderDelegate; - @Inject private LiteralDescriptions literalDescriptions; - @Inject private NativeOptionDescriptions nativeOptionDescriptions; - @Inject private Options options; + @Inject private LinkingHelper linkingHelper; + @Inject private IPreferenceStoreAccess storeAccess; + @Inject private IUriResolver uriResolver; + @Inject private IResourceScopeCache cache; + @Inject private NameResolver nameResolver; + @Inject private ProtobufQualifiedNameConverter nameConverter; + + private static final String CACHEKEY = "IMPORT_NORMALIZER_CACHE_KEY"; + + /** + * 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, + * {@code FileOptions} should be returned because it contains the declarations of the default file + * options. + */ + // TODO (atrookey) Create utility for getting the type of an Option. + private static String getOptionType(EObject context) { + if (context == null) { + return "FileOptions"; + } + if (context instanceof Message) { + return "MessageOptions"; + } + if (context instanceof Enum) { + return "EnumOptions"; + } + if (context instanceof FieldOption) { + return "FieldOptions"; + } + if (context instanceof Rpc) { + return "MethodOptions"; + } + if (context instanceof Stream) { + return "StreamOptions"; + } + if (context instanceof Literal) { + return "EnumValueOptions"; + } + return getOptionType(context.eContainer()); + } + + private Collection<IEObjectDescription> allMessages(Protobuf root) { + return modelElementFinder.find(root, complexTypeFinderDelegate, Message.class); + } + + /** + * 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(); + } + + // TODO (atrookey) Create utility for getting package. + private String getPackageOfResource(Resource resource) { + Protobuf object; + if (resource != null && (object = (Protobuf) resource.getContents().get(0)) != null) { + for (EObject content : object.getElements()) { + if (content instanceof com.google.eclipse.protobuf.protobuf.Package) { + return ((com.google.eclipse.protobuf.protobuf.Package) content).getImportedNamespace(); + } + } + } + return ""; + } + + /** + * Returns descriptor associated with the current project. + */ + private @Nullable Resource getDescriptorResource(EObject context) { + URI descriptorLocation; + IProject project = EResources.getProjectOf(context.eResource()); + IPreferenceStore store = storeAccess.getWritablePreferenceStore(project); + String rawDescriptorLocation = store.getString(PreferenceNames.DESCRIPTOR_PROTO_PATH); + descriptorLocation = + URI.createURI(uriResolver.resolveUri(rawDescriptorLocation, null, project)); + ResourceSet resourceSet = context.eResource().getResourceSet(); + Resource resource = resourceSet.getResource(descriptorLocation, true); + if (resource != null) { + return resource; + } + try { + InputStream contents = openFile(descriptorLocation); + resource = resourceSet.createResource(descriptorLocation, UNSPECIFIED_CONTENT_TYPE); + resource.load(contents, singletonMap(OPTION_ENCODING, UTF_8)); + resolveLazyCrossReferences(resource, NullImpl); + return resource; + } catch (IOException e) { + logger.log(Level.ERROR, e); + } + 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 local scope provider. + */ + private ProtobufImportedNamespaceAwareLocalScopeProvider getLocalScopeProvider() { + return (ProtobufImportedNamespaceAwareLocalScopeProvider) super.getDelegate(); + } @Override public IScope getScope(EObject context, EReference reference) { @@ -95,6 +224,86 @@ 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( + OptionSource optionSource, EReference reference, int fieldIndex, EList<OptionField> fields) { + if (fieldIndex < 0 || fields.size() <= fieldIndex) { + throw new IllegalArgumentException(); + } + IScope parentScope, retval = 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(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}. + */ + 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; + } + @Override public Collection<IEObjectDescription> potentialComplexTypesFor(MessageField field) { return modelElementFinder.find(field, complexTypeFinderDelegate, ComplexType.class); @@ -106,11 +315,20 @@ return modelElementFinder.find(root, complexTypeFinderDelegate, ExtensibleType.class); } - @SuppressWarnings("unused") - public IScope scope_MessageLink_target(MessageLink link, EReference r) { - Protobuf root = modelObjects.rootOf(link); - Collection<IEObjectDescription> messages = allMessages(root); - return createScope(messages); + @Override + public Collection<IEObjectDescription> potentialExtensionFieldNames(ComplexValue value) { + return customOptionFieldNameFinder.findFieldNamesSources( + value, extensionFieldNameFinderDelegate); + } + + @Override + public Collection<IEObjectDescription> potentialExtensionFieldsFor(AbstractCustomOption option) { + return customOptionFieldFinder.findOptionFields(option, extensionFieldFinderDelegate); + } + + @Override + public Collection<IEObjectDescription> potentialMessageFieldsFor(AbstractCustomOption option) { + return customOptionFieldFinder.findOptionFields(option, messageFieldFinderDelegate); } @Override @@ -125,67 +343,9 @@ return allMessages(root); } - private Collection<IEObjectDescription> allMessages(Protobuf root) { - return modelElementFinder.find(root, complexTypeFinderDelegate, Message.class); - } - - @SuppressWarnings("unused") - public IScope scope_LiteralLink_target(LiteralLink link, EReference r) { - EObject container = link.eContainer(); - Enum anEnum = null; - if (container instanceof DefaultValueFieldOption) { - container = container.eContainer(); - } - if (container instanceof AbstractOption) { - AbstractOption option = (AbstractOption) container; - if (options.isNative(option)) { - IProject project = EResources.getProjectOf(option.eResource()); - ProtoDescriptor descriptor = descriptorProvider.primaryDescriptor(project); - IndexedElement e = options.rootSourceOf(option); - anEnum = descriptor.enumTypeOf((MessageField) e); - } - } - if (container instanceof AbstractCustomOption) { - AbstractCustomOption option = (AbstractCustomOption) container; - container = options.sourceOf(option); - } - if (container instanceof SimpleValueField) { - SimpleValueField field = (SimpleValueField) container; - container = field.getName().getTarget(); - } - if (container instanceof MessageField) { - anEnum = messageFields.enumTypeOf((MessageField) container); - } - return createScope(literalDescriptions.literalsOf(anEnum)); - } - - @SuppressWarnings("unused") - public IScope scope_OptionSource_target(OptionSource source, EReference r) { - EObject c = source.eContainer(); - if (c instanceof AbstractOption) { - AbstractOption option = (AbstractOption) c; - if (options.isNative(option)) { - return createScope(nativeOptionDescriptions.sources(option)); - } - } - if (c instanceof AbstractCustomOption) { - AbstractCustomOption option = (AbstractCustomOption) c; - - if (c instanceof GroupElement) { - EObject container = c.eContainer(); - if (container instanceof Group) { - OptionType optionType = OptionType.findOptionTypeForLevelOf(container.eContainer()); - return createScope( - optionType != null - ? modelElementFinder.find(option, customOptionFinderDelegate, optionType) - : Collections.<IEObjectDescription>emptySet()); - } - } - - return createScope(potentialSourcesFor(option)); - } - Set<IEObjectDescription> descriptions = emptySet(); - return createScope(descriptions); + @Override + public Collection<IEObjectDescription> potentialNormalFieldNames(ComplexValue value) { + return customOptionFieldNameFinder.findFieldNamesSources(value, normalFieldNameFinderDelegate); } @Override @@ -198,75 +358,234 @@ return descriptions; } - @SuppressWarnings("unused") - public IScope scope_OptionField_target(OptionField field, EReference r) { - return createScope(allPossibleSourcesOf(field)); - } - - private Collection<IEObjectDescription> allPossibleSourcesOf(OptionField field) { - if (field == null) { - return emptySet(); - } - EObject container = field.eContainer(); - if (container instanceof AbstractCustomOption) { - AbstractCustomOption option = (AbstractCustomOption) container; - if (field instanceof MessageOptionField) { - return customOptionFieldFinder.findOptionFields(option, messageFieldFinderDelegate, field); + /** + * Recursively scopes the {@code FieldName} starting with the {@code OptionSource}. + * + * For example: + * + * <pre> + * message FooOptions { + * optional int32 opt1 = 1; + * } + * extend google.protobuf.FieldOptions { + * optional FooOptions foo_options = 1234; + * } + * message Bar { + * optional int32 b = 1 [(foo_options) = { opt1: 123 }]; + * } + * </pre> + * + * The {@code NormalFieldName} {@code opt1} contains a cross-reference to {@code FooOptions.opt1}. + */ + public IScope scope_FieldName_target(FieldName fieldName, EReference reference) { + OptionSource optionSource = null; + EObject valueField = fieldName.eContainer(); + if (valueField != null && valueField instanceof ValueField) { + EObject complexValue = valueField.eContainer(); + if (complexValue != null && 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 != null && unknownOption instanceof NativeFieldOption) { + NativeFieldOption nativeFieldOption = (NativeFieldOption) unknownOption; + optionSource = nativeFieldOption.getSource(); + } + if (unknownOption != null && unknownOption instanceof CustomFieldOption) { + CustomFieldOption customFieldOption = (CustomFieldOption) unknownOption; + optionSource = customFieldOption.getSource(); + } + if (unknownOption != null && unknownOption instanceof NativeOption) { + NativeOption option = (NativeOption) unknownOption; + optionSource = option.getSource(); + } + if (unknownOption != null && unknownOption instanceof CustomOption) { + CustomOption option = (CustomOption) unknownOption; + optionSource = option.getSource(); + } + return getScopeOfFieldName(delegatedScope, optionSource, reference); } - return customOptionFieldFinder.findOptionFields(option, extensionFieldFinderDelegate, field); - } - return emptySet(); - } - - @Override - public Collection<IEObjectDescription> potentialMessageFieldsFor(AbstractCustomOption option) { - return customOptionFieldFinder.findOptionFields(option, messageFieldFinderDelegate); - } - - @Override - public Collection<IEObjectDescription> potentialExtensionFieldsFor(AbstractCustomOption option) { - return customOptionFieldFinder.findOptionFields(option, extensionFieldFinderDelegate); - } - - @SuppressWarnings("unused") - public IScope scope_FieldName_target(FieldName name, EReference r) { - return createScope(findSources(name)); - } - - private Collection<IEObjectDescription> findSources(FieldName name) { - ComplexValue value = container(name); - if (value == null) { - return emptySet(); - } - if (name instanceof NormalFieldName) { - return potentialNormalFieldNames(value); - } - return potentialExtensionFieldNames(value); - } - - private ComplexValue container(FieldName name) { - EObject container = name; - while (container != null) { - if (container instanceof ComplexValue) { - return (ComplexValue) container; - } - container = container.eContainer(); } return null; } - @Override - public Collection<IEObjectDescription> potentialNormalFieldNames(ComplexValue value) { - return customOptionFieldNameFinder.findFieldNamesSources(value, normalFieldNameFinderDelegate); + /** + * Creates a scope containing elements of type {@code Literal} that can be + * referenced with their local name only. + * + * 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> + * + * 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; + scope = + createLiteralLinkResolvedScopeForResource( + reference, scope, getDescriptorResource(literalLink)); + scope = createLiteralLinkResolvedScopeForResource(reference, scope, literalLink.eResource()); + return scope; } - @Override - public Collection<IEObjectDescription> potentialExtensionFieldNames(ComplexValue value) { - return customOptionFieldNameFinder.findFieldNamesSources( - value, extensionFieldNameFinderDelegate); + // TODO (atrookey) Create resolvers for ProtobufImportScopes at all scope levels, + // not just top level. + private IScope createLiteralLinkResolvedScopeForResource( + EReference reference, IScope parent, Resource resource) { + IScope scope = parent; + scope = getProtobufImportScope(scope, resource, reference); + List<ImportNormalizer> protoNormalizers = + cache.get( + CACHEKEY, + resource, + new Provider<List<ImportNormalizer>>() { + @Override + public List<ImportNormalizer> get() { + return createEnumElementResolvers( + resource.getContents().get(0), getPackageOfResource(resource), false); + } + }); + if (scope instanceof ProtobufImportScope) { + for (ImportNormalizer normalizer : protoNormalizers) { + ((ProtobufImportScope) scope).addNormalizer(normalizer); + } + } else if (scope instanceof SelectableBasedScope) { + ISelectable allDescriptions = getLocalScopeProvider().getAllDescriptions(resource); + scope = + getLocalScopeProvider() + .createImportScope( + scope, protoNormalizers, allDescriptions, reference.getEReferenceType(), false); + } + return scope; } - private static IScope createScope(Iterable<IEObjectDescription> descriptions) { - return new SimpleScope(descriptions, DO_NOT_IGNORE_CASE); + /** + * Creates an {@code ImportNormalizer} for every {@code Enum} that is a descendant of {@code context}. + */ + private List<ImportNormalizer> createEnumElementResolvers( + EObject context, String qualifiedName, boolean ignoreCase) { + List<ImportNormalizer> importedNamespaceResolvers = new ArrayList<>(); + for (EObject child : context.eContents()) { + if (child instanceof Enum) { + String name = appendNameOfEObject(qualifiedName, child); + if (!name.isEmpty()) { + ImportNormalizer resolver = + getLocalScopeProvider().createImportedNamespaceResolver(name, ignoreCase); + if (resolver != null) { + importedNamespaceResolvers.add(resolver); + } + } + } + if (child instanceof Message) { + String name = appendNameOfEObject(qualifiedName, child); + importedNamespaceResolvers.addAll(createEnumElementResolvers(child, name, ignoreCase)); + } + } + return importedNamespaceResolvers; + } + + private String appendNameOfEObject(String qualifiedName, EObject child) { + String childName = nameResolver.nameOf(child); + if (qualifiedName.isEmpty()) { + return childName; + } + return qualifiedName + nameConverter.getDelimiter() + childName; + } + + /** + * Recursively scopes the {@code OptionField} starting with the {@code OptionSource}. + * + * For example: + * + * <pre> + * message Code { + * optional double number = 1; + * } + * message Type { + * optional Code code = 1; + * } + * extend proto2.FieldOptions { + * optional Type type = 1000; + * } + * message Person { + * optional bool active = 1 [(type).code.number = 68]; + * } + * </pre> + * + * The {@code OptionField} {@code number} contains a cross-reference to {@code Code.number}. + */ + public @Nullable IScope scope_OptionField_target(OptionField optionField, EReference reference) { + EObject customOption = optionField.eContainer(); + if (customOption != null) { + OptionSource optionSource = null; + EList<OptionField> fields = null; + if (customOption instanceof CustomFieldOption) { + optionSource = ((CustomFieldOption) customOption).getSource(); + fields = ((CustomFieldOption) customOption).getFields(); + } + if (customOption instanceof CustomOption) { + optionSource = ((CustomOption) customOption).getSource(); + fields = ((CustomOption) customOption).getFields(); + } + if (optionSource != null && fields != null) { + int index = fields.indexOf(optionField); + return getScopeOfOptionField(optionSource, reference, index, fields); + } + } + return null; + } + + /** + * Creates a scope containing the default options defined in descriptor.proto. + * + * For example: + * + * <pre> + * option java_package = "com.example.foo"; + * </pre> + * + * The {@code OptionSource} {@code java_package} contains a cross-reference to {@code + * google.protobuf.FileOptions.java_package} defined in descriptor.proto. + */ + public IScope scope_OptionSource_target(OptionSource optionSource, EReference reference) { + Resource descriptorResource = getDescriptorResource(optionSource); + String descriptorMessage = + getPackageOfResource(descriptorResource) + + nameConverter.getDelimiter() + + getOptionType(optionSource); + ImportNormalizer normalizer = + getLocalScopeProvider().createImportedNamespaceResolver(descriptorMessage, false); + IScope scope = delegateGetScope(optionSource, reference); + scope = getProtobufImportScope(scope, getDescriptorResource(optionSource), reference); + ((ProtobufImportScope) scope).addNormalizer(normalizer); + return scope; + } + + /** Returns the top level scope of the {@code Resource}. */ + private ProtobufImportScope getProtobufImportScope( + IScope parent, Resource resource, EReference reference) { + EObject protobuf = resource.getContents().get(0); + IScope descriptorResourceScope = + getLocalScopeProvider().getResourceScope(parent, protobuf, reference); + return (ProtobufImportScope) + getLocalScopeProvider().getLocalElementsScope(descriptorResourceScope, protobuf, reference); } }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufSelectableBasedScope.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufSelectableBasedScope.java new file mode 100644 index 0000000..40f6e00 --- /dev/null +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufSelectableBasedScope.java
@@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016 Google Inc. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package com.google.eclipse.protobuf.scoping; + +import static com.google.eclipse.protobuf.model.util.QualifiedNames.removeLeadingDot; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.resource.ISelectable; +import org.eclipse.xtext.scoping.IScope; +import org.eclipse.xtext.scoping.impl.SelectableBasedScope; + +import com.google.common.base.Predicate; + +/** + * {@link SelectableBasedScope} that handles qualified names with a leading dot. + * + * @author (atrookey@google.com) Alexander Rookey + */ +public class ProtobufSelectableBasedScope extends SelectableBasedScope { + public static IScope createScope( + IScope outer, ISelectable selectable, EClass type, boolean ignoreCase) { + return createScope(outer, selectable, null, type, ignoreCase); + } + + public static IScope createScope( + IScope outer, + ISelectable selectable, + Predicate<IEObjectDescription> filter, + EClass type, + boolean ignoreCase) { + if (selectable == null || selectable.isEmpty()) { + return outer; + } + return new ProtobufSelectableBasedScope(outer, selectable, filter, type, ignoreCase); + } + + protected ProtobufSelectableBasedScope( + IScope outer, + ISelectable selectable, + Predicate<IEObjectDescription> filter, + EClass type, + boolean ignoreCase) { + super(outer, selectable, filter, type, ignoreCase); + } + + /** Before getting element, removes leading dot. */ + @Override + public IEObjectDescription getSingleElement(QualifiedName name) { + return super.getSingleElement(removeLeadingDot(name)); + } +}