Support "reserved" field numbers and names. Change-Id: I48f2c796178fc44aa7295b84f974e5acbe833eda
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/XtextRule.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/XtextRule.java index e20c5c6..31a87a6 100644 --- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/XtextRule.java +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/XtextRule.java
@@ -10,10 +10,10 @@ package com.google.eclipse.protobuf.junit.core; import static java.util.Arrays.asList; - import static org.eclipse.xtext.util.Strings.isEmpty; import java.io.File; +import java.util.Collection; import java.util.List; import org.eclipse.emf.ecore.EObject; @@ -133,8 +133,11 @@ return finder.find(text); } + public <T extends EObject> List<T> findAll(Class<T> type) { + return EcoreUtil2.getAllContentsOfType(root, type); + } + public <T extends EObject> T findFirst(Class<T> type) { - List<T> contents = EcoreUtil2.getAllContentsOfType(root, type); - return (contents.isEmpty()) ? null : contents.get(0); + return findAll(type).get(0); } }
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForIndexRangeConflicts_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForIndexRangeConflicts_Test.java new file mode 100644 index 0000000..a29c8c3 --- /dev/null +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForIndexRangeConflicts_Test.java
@@ -0,0 +1,142 @@ +/* + * Copyright (c) 2015 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.validation; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule; +import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; + +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.xtext.validation.ValidationMessageAcceptor; +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.Group; +import com.google.eclipse.protobuf.protobuf.IndexRange; +import com.google.eclipse.protobuf.protobuf.Message; +import com.google.eclipse.protobuf.protobuf.MessageField; +import com.google.eclipse.protobuf.protobuf.ProtobufPackage; +import com.google.inject.Inject; + +public class ProtobufJavaValidator_checkForIndexRangeConflicts_Test { + @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule()); + + @Inject private ProtobufJavaValidator validator; + private ValidationMessageAcceptor messageAcceptor; + + @Before public void setUp() { + messageAcceptor = mock(ValidationMessageAcceptor.class); + validator.setMessageAcceptor(messageAcceptor); + } + + // syntax = "proto2"; + // + // message Person { + // reserved 10, 20 to 30; + // reserved 10; + // reserved 20; + // reserved 25 to 26; + // reserved 30 to 31; + // } + @Test public void should_error_on_conflict_between_reserved_and_reserved() { + validator.checkForIndexRangeConflicts(xtext.findFirst(Message.class)); + List<IndexRange> indexRanges = xtext.findAll(IndexRange.class); + verifyError("10 conflicts with reserved 10", indexRanges.get(2)); + verifyError("20 conflicts with reserved 20 to 30", indexRanges.get(3)); + verifyError("25 to 26 conflicts with reserved 20 to 30", indexRanges.get(4)); + verifyError("30 to 31 conflicts with reserved 20 to 30", indexRanges.get(5)); + } + + // syntax = "proto2"; + // + // message Person { + // reserved 10, 20 to 30; + // extensions 10, 15 to max; + // } + @Test public void should_error_on_conflict_between_reserved_and_extensions() { + validator.checkForIndexRangeConflicts(xtext.findFirst(Message.class)); + List<IndexRange> indexRanges = xtext.findAll(IndexRange.class); + verifyError("10 conflicts with reserved 10", indexRanges.get(2)); + verifyError("15 to max conflicts with reserved 20 to 30", indexRanges.get(3)); + } + + // syntax = "proto2"; + // + // message Person { + // reserved 10, 20 to 30; + // optional bool foo = 10; + // optional bool bar = 25; + // group baz = 26 { + // optional bool a = 27; + // } + // } + @Test public void should_error_on_conflict_between_reserved_and_indexed_element() { + validator.checkForIndexRangeConflicts(xtext.findFirst(Message.class)); + verifyError( + "10 conflicts with reserved 10", + xtext.findAll(MessageField.class).get(0), + ProtobufPackage.Literals.MESSAGE_FIELD__INDEX); + verifyError( + "25 conflicts with reserved 20 to 30", + xtext.findAll(MessageField.class).get(1), + ProtobufPackage.Literals.MESSAGE_FIELD__INDEX); + verifyError( + "26 conflicts with reserved 20 to 30", + xtext.findAll(Group.class).get(0), + ProtobufPackage.Literals.GROUP__INDEX); + verifyError( + "27 conflicts with reserved 20 to 30", + xtext.findAll(MessageField.class).get(2), + ProtobufPackage.Literals.MESSAGE_FIELD__INDEX); + } + + // syntax = "proto2"; + // + // message Person { + // extensions 10, 20 to 30; + // optional bool foo = 10; + // optional bool bar = 25; + // group baz = 26 { + // optional bool a = 27; + // } + // } + @Test public void should_error_on_conflict_between_extensions_and_indexed_element() { + validator.checkForIndexRangeConflicts(xtext.findFirst(Message.class)); + verifyError( + "10 conflicts with extensions 10", + xtext.findAll(MessageField.class).get(0), + ProtobufPackage.Literals.MESSAGE_FIELD__INDEX); + verifyError( + "25 conflicts with extensions 20 to 30", + xtext.findAll(MessageField.class).get(1), + ProtobufPackage.Literals.MESSAGE_FIELD__INDEX); + verifyError( + "26 conflicts with extensions 20 to 30", + xtext.findAll(Group.class).get(0), + ProtobufPackage.Literals.GROUP__INDEX); + verifyError( + "27 conflicts with extensions 20 to 30", + xtext.findAll(MessageField.class).get(2), + ProtobufPackage.Literals.MESSAGE_FIELD__INDEX); + } + + private void verifyError(String message, EObject errorSource) { + verifyError(message, errorSource, null); + } + + private void verifyError(String message, EObject errorSource, EStructuralFeature errorFeature) { + verify(messageAcceptor).acceptError(message, errorSource, errorFeature, -1, null); + } +}
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedIndexAndName_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedIndexAndName_Test.java new file mode 100644 index 0000000..9316595 --- /dev/null +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedIndexAndName_Test.java
@@ -0,0 +1,63 @@ +/* + * Copyright (c) 2015 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.validation; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule; +import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.xtext.validation.ValidationMessageAcceptor; +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.Reserved; +import com.google.inject.Inject; + +public class ProtobufJavaValidator_checkForReservedIndexAndName_Test { + private static final String ERROR_MESSAGE = "A reserved declaration may not include both numbers and names."; + + @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule()); + + @Inject private ProtobufJavaValidator validator; + private ValidationMessageAcceptor messageAcceptor; + + @Before public void setUp() { + messageAcceptor = mock(ValidationMessageAcceptor.class); + validator.setMessageAcceptor(messageAcceptor); + } + + // syntax = "proto2"; + // + // message Person { + // reserved 1, "foo"; + // } + @Test public void should_error_on_reserved_index_and_name() { + validator.checkForReservedIndexAndName(xtext.findFirst(Reserved.class)); + verifyError(ERROR_MESSAGE, xtext.findFirst(Reserved.class), null); + } + + // syntax = "proto2"; + // + // message Person { + // reserved "foo", 1; + // } + @Test public void should_error_on_reserved_name_and_index() { + validator.checkForReservedIndexAndName(xtext.findFirst(Reserved.class)); + verifyError(ERROR_MESSAGE, xtext.findFirst(Reserved.class), null); + } + + private void verifyError(String message, EObject errorSource, EStructuralFeature errorFeature) { + verify(messageAcceptor).acceptError(message, errorSource, errorFeature, -1, null); + } +}
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedNameConflicts_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedNameConflicts_Test.java new file mode 100644 index 0000000..307d127 --- /dev/null +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedNameConflicts_Test.java
@@ -0,0 +1,89 @@ +/* + * Copyright (c) 2015 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.validation; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule; +import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; + +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.xtext.validation.ValidationMessageAcceptor; +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.Group; +import com.google.eclipse.protobuf.protobuf.Message; +import com.google.eclipse.protobuf.protobuf.MessageField; +import com.google.eclipse.protobuf.protobuf.ProtobufPackage; +import com.google.eclipse.protobuf.protobuf.StringLiteral; +import com.google.inject.Inject; + +public class ProtobufJavaValidator_checkForReservedNameConflicts_Test { + @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule()); + + @Inject private ProtobufJavaValidator validator; + private ValidationMessageAcceptor messageAcceptor; + + @Before public void setUp() { + messageAcceptor = mock(ValidationMessageAcceptor.class); + validator.setMessageAcceptor(messageAcceptor); + } + + // syntax = "proto2"; + // + // message Person { + // reserved "foo", "bar"; + // reserved "foo", 'b' 'a' 'r'; + // } + @Test public void should_error_on_conflict_between_reserved_and_reserved() { + validator.checkForReservedNameConflicts(xtext.findFirst(Message.class)); + List<StringLiteral> stringLiterals = xtext.findAll(StringLiteral.class); + verifyError("\"foo\" conflicts with reserved \"foo\"", stringLiterals.get(3)); + verifyError("\"bar\" conflicts with reserved \"bar\"", stringLiterals.get(4)); + } + + // syntax = "proto2"; + // + // message Person { + // reserved "foo", "bar", "baz"; + // optional bool foo = 1; + // group bar = 2 { + // optional bool baz = 3; + // } + // } + @Test public void should_error_on_conflict_between_reserved_and_indexed_element() { + validator.checkForReservedNameConflicts(xtext.findFirst(Message.class)); + verifyError( + "\"foo\" conflicts with reserved \"foo\"", + xtext.findAll(MessageField.class).get(0), + ProtobufPackage.Literals.MESSAGE_FIELD__NAME); + verifyError( + "\"bar\" conflicts with reserved \"bar\"", + xtext.findAll(Group.class).get(0), + ProtobufPackage.Literals.COMPLEX_TYPE__NAME); + verifyError( + "\"baz\" conflicts with reserved \"baz\"", + xtext.findAll(MessageField.class).get(1), + ProtobufPackage.Literals.MESSAGE_FIELD__NAME); + } + + private void verifyError(String message, EObject errorSource) { + verifyError(message, errorSource, null); + } + + private void verifyError(String message, EObject errorSource, EStructuralFeature errorFeature) { + verify(messageAcceptor).acceptError(message, errorSource, errorFeature, -1, null); + } +}
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedToMax_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedToMax_Test.java new file mode 100644 index 0000000..201a2b4 --- /dev/null +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedToMax_Test.java
@@ -0,0 +1,56 @@ +/* + * Copyright (c) 2015 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.validation; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule; +import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.xtext.validation.ValidationMessageAcceptor; +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.IndexRange; +import com.google.eclipse.protobuf.protobuf.ProtobufPackage; +import com.google.eclipse.protobuf.protobuf.Reserved; +import com.google.inject.Inject; + +public class ProtobufJavaValidator_checkForReservedToMax_Test { + @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule()); + + @Inject private ProtobufJavaValidator validator; + private ValidationMessageAcceptor messageAcceptor; + + @Before public void setUp() { + messageAcceptor = mock(ValidationMessageAcceptor.class); + validator.setMessageAcceptor(messageAcceptor); + } + + // syntax = "proto2"; + // + // message Person { + // reserved 1, 2 to max; + // } + @Test public void should_error_on_reserved_to_max() { + validator.checkForReservedToMax(xtext.findFirst(Reserved.class)); + verifyError( + "Reserved index range must have finite upper bound.", + xtext.findAll(IndexRange.class).get(1), + ProtobufPackage.Literals.INDEX_RANGE__TO); + } + + private void verifyError(String message, EObject errorSource, EStructuralFeature errorFeature) { + verify(messageAcceptor).acceptError(message, errorSource, errorFeature, -1, null); + } +}
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/commands/semicolon/SmartSemicolonHandlerTest.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/commands/semicolon/SmartSemicolonHandlerTest.java index 1956883..def91de 100644 --- a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/commands/semicolon/SmartSemicolonHandlerTest.java +++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/commands/semicolon/SmartSemicolonHandlerTest.java
@@ -11,7 +11,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.custom.StyledTextContent; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; @@ -19,7 +18,6 @@ import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.junit.Rule; import org.junit.Test; -import org.mockito.Mock; import org.mockito.Mockito; import com.google.eclipse.protobuf.junit.core.XtextRule; @@ -117,6 +115,32 @@ // syntax = "proto2"; // // message Message { + // optional bool foo = 1; + // reserved 3; + // optional bool incomplete + // } + @Test public void shouldDetermineCorrectIndexWithSingleReserved() { + MessageField incomplete = xtext.find("incomplete", MessageField.class); + assertThat(handler.determineNewIndex(incomplete), is(4L)); + } + + // // ignore errors + // syntax = "proto2"; + // + // message Message { + // optional bool foo = 1; + // reserved 3, 5 to 7; + // optional bool incomplete + // } + @Test public void shouldDetermineCorrectIndexWithReservedRange() { + MessageField incomplete = xtext.find("incomplete", MessageField.class); + assertThat(handler.determineNewIndex(incomplete), is(8L)); + } + + // // ignore errors + // syntax = "proto2"; + // + // message Message { // optional bool incomplete // } @Test public void shouldComplete() {
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Labels.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Labels.java index 69ca6ad..89c2d00 100644 --- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Labels.java +++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Labels.java
@@ -21,12 +21,12 @@ import com.google.eclipse.protobuf.protobuf.ExtensibleTypeLink; import com.google.eclipse.protobuf.protobuf.Extensions; import com.google.eclipse.protobuf.protobuf.Import; +import com.google.eclipse.protobuf.protobuf.IndexRange; import com.google.eclipse.protobuf.protobuf.IndexedElement; import com.google.eclipse.protobuf.protobuf.Literal; import com.google.eclipse.protobuf.protobuf.MessageField; import com.google.eclipse.protobuf.protobuf.MessageLink; import com.google.eclipse.protobuf.protobuf.OptionField; -import com.google.eclipse.protobuf.protobuf.Range; import com.google.eclipse.protobuf.protobuf.Rpc; import com.google.eclipse.protobuf.protobuf.Stream; import com.google.eclipse.protobuf.protobuf.TypeExtension; @@ -110,13 +110,13 @@ private Object labelFor(Extensions extensions) { StringBuilder builder = new StringBuilder(); - EList<Range> ranges = extensions.getRanges(); + EList<IndexRange> ranges = extensions.getRanges(); int rangeCount = ranges.size(); for (int i = 0; i < rangeCount; i++) { if (i > 0) { builder.append(", "); } - Range range = ranges.get(i); + IndexRange range = ranges.get(i); builder.append(range.getFrom()); String to = range.getTo(); if (to != null) {
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/Protobuf.xtext b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/Protobuf.xtext index 0627375..fe0b46f 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/Protobuf.xtext +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/Protobuf.xtext
@@ -50,12 +50,12 @@ '}' (';')?; MessageElement: - =>Option | Extensions | ComplexType | MessageField | TypeExtension | OneOf; + =>Option | Extensions | ComplexType | MessageField | TypeExtension | OneOf | Reserved; -Range: - from=LONG ('to' to=RangeMax)?; +IndexRange: + from=LONG ('to' to=IndexRangeMax)?; -RangeMax: +IndexRangeMax: LONG | 'max'; // Hack to make the default modifier 'unspecified' @@ -84,7 +84,7 @@ Extensions | Group | MessageField; Extensions: - ->'extensions' ranges+=Range (',' ranges+=Range)* (';')+; + ->'extensions' ranges+=IndexRange (',' ranges+=IndexRange)* (';')+; MessageField: (modifier=Modifier)? =>type=TypeLink name=Name '=' index=(LONG | HEX) @@ -135,6 +135,12 @@ ExtensibleType: Message | Group; +Reserved: + 'reserved' reservations+=Reservation (',' reservations+=Reservation)* ';'+; + +Reservation: + IndexRange | StringLiteral; + Service: ->'service' name=Name '{' (elements+=ServiceElement)* @@ -250,7 +256,7 @@ 'package' | 'import' | 'public' | 'option' | 'extend' | 'message' | 'optional' | 'required' | 'repeated' | 'enum' | 'service' | 'rpc' | 'stream' | 'returns' | 'default' | 'extensions' | 'to' | 'max' | 'true' | 'false' | 'double' | 'float' | 'int32' | 'int64' | 'uint32' | 'uint64' | 'sint32' | 'sint64' | 'fixed32' | 'fixed64' | - 'sfixed32' | 'sfixed64' | 'bool' | 'string' | 'bytes' | 'weak' | 'map'; + 'sfixed32' | 'sfixed64' | 'bool' | 'string' | 'bytes' | 'weak' | 'map' | 'reserved'; SimpleValueLink: LiteralLink | BooleanLink | NumberLink | StringLink;
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexRanges.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexRanges.java new file mode 100644 index 0000000..47aee62 --- /dev/null +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexRanges.java
@@ -0,0 +1,49 @@ +/* + * Copyright (c) 2015 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.model.util; + +import com.google.common.collect.Range; +import com.google.eclipse.protobuf.protobuf.IndexRange; +import com.google.eclipse.protobuf.services.ProtobufGrammarAccess; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * Utility methods related to <code>{@link IndexRange}</code>. + * + * @author jogl@google.com (John Glassmyer) + */ +@Singleton public class IndexRanges { + private ProtobufGrammarAccess protobufGrammarAccess; + + @Inject + IndexRanges(ProtobufGrammarAccess protobufGrammarAccess) { + this.protobufGrammarAccess = protobufGrammarAccess; + } + + public Range<Long> toLongRange(IndexRange indexRange) { + long from = indexRange.getFrom(); + + Range<Long> range; + String toString = indexRange.getTo(); + if (toString == null) { + range = Range.singleton(from); + } else if (toString.equals(getMaxKeyword())) { + range = Range.atLeast(from); + } else { + range = Range.closed(from, Long.valueOf(toString)); + } + + return range; + } + + public String getMaxKeyword() { + return protobufGrammarAccess.getIndexRangeMaxAccess().getMaxKeyword_1().getValue(); + } +}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexedElements.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexedElements.java index 3061b1f..1b995d4 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexedElements.java +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexedElements.java
@@ -19,13 +19,15 @@ import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.util.SimpleAttributeResolver; +import com.google.common.collect.Iterables; +import com.google.common.collect.Range; import com.google.eclipse.protobuf.protobuf.FieldOption; import com.google.eclipse.protobuf.protobuf.Group; +import com.google.eclipse.protobuf.protobuf.IndexRange; import com.google.eclipse.protobuf.protobuf.IndexedElement; import com.google.eclipse.protobuf.protobuf.Message; -import com.google.eclipse.protobuf.protobuf.MessageElement; -import com.google.eclipse.protobuf.protobuf.MessageField; import com.google.eclipse.protobuf.protobuf.OneOf; +import com.google.eclipse.protobuf.protobuf.Reserved; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -37,6 +39,7 @@ @Singleton public class IndexedElements { private final static SimpleAttributeResolver<EObject, Long> INDEX_RESOLVER = newResolver(long.class, "index"); + @Inject private IndexRanges indexRanges; @Inject private ModelObjects modelObjects; /** @@ -72,6 +75,14 @@ if (e instanceof Group) { maxIndex = max(maxIndex, findMaxIndex(((Group) e).getElements())); } + } else if (e instanceof Reserved) { + for (IndexRange indexRange : + Iterables.filter(((Reserved) e).getReservations(), IndexRange.class)) { + Range<Long> range = indexRanges.toLongRange(indexRange); + if (range.hasUpperBound()) { + maxIndex = max(maxIndex, range.upperEndpoint()); + } + } } }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.java index 3f6c7da..a54836c 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.java +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.java
@@ -14,6 +14,8 @@ * @author alruiz@google.com (Alex Ruiz) */ public class Messages extends NLS { + public static String conflictsWithExtensions; + public static String conflictsWithReserved; public static String expectedFieldName; public static String expectedFieldNumber; public static String expectedIdentifier; @@ -37,6 +39,8 @@ public static String multiplePackages; public static String oneofFieldWithModifier; public static String requiredInProto3; + public static String reservedIndexAndName; + public static String reservedToMax; public static String scopingError; public static String unknownSyntax; public static String unrecognizedSyntaxIdentifier;
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.properties b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.properties index 4bdeb24..7f46818 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.properties +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.properties
@@ -20,7 +20,11 @@ missingModifier = Missing modifier: \"required\", \"optional\", or \"repeated\". oneofFieldWithModifier = Fields inside "oneof" cannot have a modifier. multiplePackages = Multiple package definitions. +conflictsWithExtensions = %s conflicts with extensions %s +conflictsWithReserved = %s conflicts with reserved %s requiredInProto3 = Required fields are not allowed in proto3. +reservedIndexAndName = A reserved declaration may not include both numbers and names. +reservedToMax = Reserved index range must have finite upper bound. scopingError = It may be caused by an imported non-proto2 file. unknownSyntax = Unknown syntax. This parser only recognizes \"proto2\" or \"proto3\". unrecognizedSyntaxIdentifier = Unrecognized syntax identifier \"%s\". This parser only recognizes \"proto2\" and \"proto3\".
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java index bcbbab0..00b0350 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java
@@ -13,6 +13,8 @@ import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.MESSAGE_FIELD__MODIFIER; import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.PACKAGE__NAME; import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.SYNTAX__NAME; +import static com.google.eclipse.protobuf.validation.Messages.conflictsWithExtensions; +import static com.google.eclipse.protobuf.validation.Messages.conflictsWithReserved; import static com.google.eclipse.protobuf.validation.Messages.expectedFieldNumber; import static com.google.eclipse.protobuf.validation.Messages.expectedSyntaxIdentifier; import static com.google.eclipse.protobuf.validation.Messages.fieldNumberAlreadyUsed; @@ -25,14 +27,28 @@ import static com.google.eclipse.protobuf.validation.Messages.multiplePackages; import static com.google.eclipse.protobuf.validation.Messages.oneofFieldWithModifier; import static com.google.eclipse.protobuf.validation.Messages.requiredInProto3; +import static com.google.eclipse.protobuf.validation.Messages.reservedIndexAndName; +import static com.google.eclipse.protobuf.validation.Messages.reservedToMax; import static com.google.eclipse.protobuf.validation.Messages.unknownSyntax; import static com.google.eclipse.protobuf.validation.Messages.unrecognizedSyntaxIdentifier; import static java.lang.String.format; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Range; +import com.google.eclipse.protobuf.model.util.IndexRanges; import com.google.eclipse.protobuf.model.util.IndexedElements; import com.google.eclipse.protobuf.model.util.Protobufs; +import com.google.eclipse.protobuf.model.util.StringLiterals; import com.google.eclipse.protobuf.model.util.Syntaxes; import com.google.eclipse.protobuf.naming.NameResolver; +import com.google.eclipse.protobuf.protobuf.Extensions; +import com.google.eclipse.protobuf.protobuf.IndexRange; import com.google.eclipse.protobuf.protobuf.IndexedElement; import com.google.eclipse.protobuf.protobuf.MapType; import com.google.eclipse.protobuf.protobuf.MapTypeLink; @@ -44,17 +60,24 @@ import com.google.eclipse.protobuf.protobuf.Package; import com.google.eclipse.protobuf.protobuf.Protobuf; import com.google.eclipse.protobuf.protobuf.ProtobufElement; +import com.google.eclipse.protobuf.protobuf.ProtobufPackage; +import com.google.eclipse.protobuf.protobuf.Reservation; +import com.google.eclipse.protobuf.protobuf.Reserved; import com.google.eclipse.protobuf.protobuf.ScalarType; import com.google.eclipse.protobuf.protobuf.ScalarTypeLink; +import com.google.eclipse.protobuf.protobuf.StringLiteral; import com.google.eclipse.protobuf.protobuf.Syntax; import com.google.eclipse.protobuf.protobuf.TypeExtension; import com.google.eclipse.protobuf.protobuf.TypeLink; import com.google.inject.Inject; +import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.naming.IQualifiedNameProvider; import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.util.SimpleAttributeResolver; import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.ComposedChecks; @@ -74,7 +97,9 @@ public static final String ONEOF_FIELD_WITH_MODIFIER_ERROR = "oneofFieldWithModifier"; @Inject private IndexedElements indexedElements; + @Inject private IndexRanges indexRanges; @Inject private NameResolver nameResolver; + @Inject private StringLiterals stringLiterals; @Inject private Protobufs protobufs; @Inject private IQualifiedNameProvider qualifiedNameProvider; @Inject private Syntaxes syntaxes; @@ -94,6 +119,127 @@ error(msg, syntax, SYNTAX__NAME, SYNTAX_IS_NOT_KNOWN_ERROR); } + @Check public void checkForIndexRangeConflicts(Message message) { + Collection<Range<Long>> reservedRanges = new ArrayList<>(); + for (Reserved reserved : getOwnedElements(Message.class, message, Reserved.class)) { + for (IndexRange indexRange : Iterables.filter(reserved.getReservations(), IndexRange.class)) { + Range<Long> range = indexRanges.toLongRange(indexRange); + errorOnConflicts(range, reservedRanges, conflictsWithReserved, indexRange, null); + reservedRanges.add(range); + } + } + + Collection<Range<Long>> extensionsRanges = new ArrayList<>(); + for (Extensions extensions : getOwnedElements(Message.class, message, Extensions.class)) { + for (IndexRange indexRange : extensions.getRanges()) { + Range<Long> range = indexRanges.toLongRange(indexRange); + errorOnConflicts(range, reservedRanges, conflictsWithReserved, indexRange, null); + errorOnConflicts(range, extensionsRanges, conflictsWithExtensions, indexRange, null); + extensionsRanges.add(range); + } + } + + for (IndexedElement element : getOwnedElements(Message.class, message, IndexedElement.class)) { + long index = indexedElements.indexOf(element); + Range<Long> range = Range.singleton(index); + EStructuralFeature indexFeature = indexedElements.indexFeatureOf(element); + errorOnConflicts(range, reservedRanges, conflictsWithReserved, element, indexFeature); + errorOnConflicts(range, extensionsRanges, conflictsWithExtensions, element, indexFeature); + } + } + + private void errorOnConflicts( + Range<Long> range, + Iterable<Range<Long>> existingRanges, + String errorTemplate, + EObject errorSource, + EStructuralFeature errorFeature) { + for (Range<Long> existingRange : existingRanges) { + if (range.isConnected(existingRange)) { + String message = + String.format(errorTemplate, rangeToString(range), rangeToString(existingRange)); + error(message, errorSource, errorFeature); + } + } + } + + private String rangeToString(Range<Long> range) { + if (range.hasLowerBound() && range.hasUpperBound() + && range.lowerEndpoint() == range.upperEndpoint()) { + return String.valueOf(range.lowerEndpoint()); + } + + String upper = + range.hasUpperBound() ? String.valueOf(range.upperEndpoint()) : indexRanges.getMaxKeyword(); + return String.format("%d to %s", range.lowerEndpoint(), upper); + } + + @Check public void checkForReservedToMax(Reserved reserved) { + for (IndexRange range : Iterables.filter(reserved.getReservations(), IndexRange.class)) { + String to = range.getTo(); + if (indexRanges.getMaxKeyword().equals(to)) { + error(reservedToMax, range, ProtobufPackage.Literals.INDEX_RANGE__TO); + } + } + } + + @Check public void checkForReservedNameConflicts(Message message) { + Set<String> reservedNames = new HashSet<>(); + for (Reserved reserved : getOwnedElements(Message.class, message, Reserved.class)) { + for (StringLiteral stringLiteral : + Iterables.filter(reserved.getReservations(), StringLiteral.class)) { + String name = stringLiterals.getCombinedString(stringLiteral); + reportReservedNameConflicts(name, reservedNames, stringLiteral, null); + reservedNames.add(name); + } + } + + for (IndexedElement element : getOwnedElements(Message.class, message, IndexedElement.class)) { + String name = nameResolver.nameOf(element); + if (name != null) { + EAttribute nameAttribute = SimpleAttributeResolver.NAME_RESOLVER.getAttribute(element); + reportReservedNameConflicts(name, reservedNames, element, nameAttribute); + } + } + } + + @Check public void checkForReservedIndexAndName(Reserved reserved) { + boolean hasIndexReservation = false; + boolean hasNameReservation = false; + for (Reservation reservation : reserved.getReservations()) { + if (reservation instanceof IndexRange) { + hasIndexReservation = true; + } else if (reservation instanceof StringLiteral) { + hasNameReservation = true; + } + } + + if (hasIndexReservation && hasNameReservation) { + error(reservedIndexAndName, reserved, null); + } + } + + private void reportReservedNameConflicts( + String name, Set<String> reservedNames, EObject errorSource, EAttribute errorFeature) { + if (reservedNames.contains(name)) { + String quotedName = '"' + name + '"'; + String message = String.format(conflictsWithReserved, quotedName, quotedName); + error(message, errorSource, errorFeature); + } + } + + private <E extends EObject, C extends EObject> Collection<E> getOwnedElements( + Class<C> containerType, C container, Class<E> elementType) { + List<E> allElements = EcoreUtil2.getAllContentsOfType(container, elementType); + List<E> ownedElements = new ArrayList<>(allElements.size()); + for (E element : allElements) { + if (EcoreUtil2.getContainerOfType(element, containerType) == container) { + ownedElements.add(element); + } + } + return ownedElements; + } + @Check public void checkTagNumberIsUnique(IndexedElement e) { if (isNameNull(e)) { return; // we already show an error if name is null, no need to go further.