In progress: [Issue 164] Validate that the default value of a field
matches the type of such field.
Added "quick fix" for errors found in default values of numeric fields.
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 0b445be..6cccc7e 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
@@ -73,10 +73,8 @@
boolean ignoreSyntaxErrors = shouldIgnoreSyntaxErrorsIn(text);
resource = resourceFrom(new StringInputStream(text));
IParseResult parseResult = resource.getParseResult();
- if (ignoreSyntaxErrors) {
- root = (Protobuf) parseResult.getRootASTElement();
- return;
- }
+ root = (Protobuf) parseResult.getRootASTElement();
+ if (ignoreSyntaxErrors) return;
if (!parseResult.hasSyntaxErrors()) {
if (root.getSyntax() == null) {
throw new IllegalStateException("Please specify 'proto2' syntax");
diff --git a/com.google.eclipse.protobuf.ui/icons/change.gif b/com.google.eclipse.protobuf.ui/icons/change.gif
new file mode 100644
index 0000000..068e18d
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/icons/change.gif
Binary files differ
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/grammar/CompoundElement.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/grammar/CompoundElement.java
index 258065a..d21b959 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/grammar/CompoundElement.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/grammar/CompoundElement.java
@@ -9,6 +9,7 @@
package com.google.eclipse.protobuf.ui.grammar;
import static com.google.eclipse.protobuf.grammar.CommonKeyword.*;
+import static com.google.eclipse.protobuf.grammar.ValidSyntax.proto2;
import static com.google.eclipse.protobuf.util.CommonWords.space;
import com.google.eclipse.protobuf.grammar.CommonKeyword;
@@ -26,7 +27,7 @@
EMPTY_STRING(join(QUOTE, QUOTE)),
DEFAULT_EQUAL_STRING(joinWithWhitespace(DEFAULT_EQUAL, EMPTY_STRING)),
DEFAULT_EQUAL_STRING_IN_BRACKETS(inBrackets(DEFAULT_EQUAL_STRING)),
- PROTO2_IN_QUOTES(join(QUOTE, "proto2", QUOTE, SEMICOLON)),
+ PROTO2_IN_QUOTES(join(QUOTE, proto2(), QUOTE, SEMICOLON)),
EQUAL_PROTO2_IN_QUOTES(joinWithWhitespace(EQUAL, PROTO2_IN_QUOTES));
private final String value;
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/Messages.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/Messages.java
index b1048ba..6a1d456 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/Messages.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/Messages.java
@@ -16,11 +16,10 @@
*/
public class Messages extends NLS {
- public static String changeToProto2;
- public static String changeToProto2Label;
- public static String regenerateTagNumber;
+ public static String changeValueDescription;
+ public static String changeValueLabel;
public static String regenerateTagNumberLabel;
- public static String removeDuplicatePackage;
+ public static String regenerateTagNumberDescription;
public static String removeDuplicatePackageLabel;
static {
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/Messages.properties b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/Messages.properties
index 21e5a91..efbe0b8 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/Messages.properties
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/Messages.properties
@@ -1,6 +1,5 @@
-changeToProto2=Change syntax to "proto2."
-changeToProto2Label=Change syntax to "proto2"
-regenerateTagNumber=Regenerate tag number.
+changeValueDescription=%s = %s
+changeValueLabel=Change value to '%s'
regenerateTagNumberLabel=Regenerate tag number
-removeDuplicatePackage=Remove duplicate package declaration.
+regenerateTagNumberDescription=Regenerate tag number.
removeDuplicatePackageLabel=Remove duplicate package declaration
\ No newline at end of file
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/ProtobufQuickfixProvider.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/ProtobufQuickfixProvider.java
index e2e2626..bd694c6 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/ProtobufQuickfixProvider.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/ProtobufQuickfixProvider.java
@@ -1,27 +1,36 @@
/*
* Copyright (c) 2011 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
+ * 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.ui.quickfix;
+import static com.google.eclipse.protobuf.grammar.ValidSyntax.proto2;
+import static com.google.eclipse.protobuf.protobuf.BOOL.*;
import static com.google.eclipse.protobuf.ui.quickfix.Messages.*;
+import static com.google.eclipse.protobuf.util.Strings.quote;
+import static com.google.eclipse.protobuf.validation.DataTypeValidator.*;
import static com.google.eclipse.protobuf.validation.ProtobufJavaValidator.*;
import static org.eclipse.emf.ecore.util.EcoreUtil.remove;
+import static org.eclipse.xtext.nodemodel.util.NodeModelUtils.findActualNodeFor;
import org.eclipse.emf.ecore.EObject;
+import org.eclipse.xtext.nodemodel.INode;
+import org.eclipse.xtext.resource.XtextResource;
+import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.ui.editor.model.edit.*;
import org.eclipse.xtext.ui.editor.quickfix.*;
+import org.eclipse.xtext.util.concurrent.IUnitOfWork;
import org.eclipse.xtext.validation.Issue;
-import com.google.eclipse.protobuf.model.util.IndexedElements;
+import com.google.eclipse.protobuf.grammar.CommonKeyword;
+import com.google.eclipse.protobuf.model.util.*;
+import com.google.eclipse.protobuf.naming.NameResolver;
import com.google.eclipse.protobuf.protobuf.*;
import com.google.eclipse.protobuf.protobuf.Package;
-import com.google.eclipse.protobuf.ui.labeling.Images;
import com.google.inject.Inject;
/**
@@ -29,42 +38,118 @@
*/
public class ProtobufQuickfixProvider extends DefaultQuickfixProvider {
+ private static final String ICON_FOR_CHANGE = "change.gif";
+
@Inject private IndexedElements indexedElements;
- @Inject private Images images;
+ @Inject private NameResolver nameResolver;
+ @Inject private INodes nodes;
@Fix(SYNTAX_IS_NOT_PROTO2_ERROR)
- public void makeSyntaxProto2(Issue issue, IssueResolutionAcceptor acceptor) {
- String image = images.imageFor(Syntax.class);
- acceptor.accept(issue, changeToProto2Label, changeToProto2, image, new ISemanticModification() {
+ public void changeSyntaxToProto2(Issue issue, IssueResolutionAcceptor acceptor) {
+ ISemanticModification modification = new ISemanticModification() {
@Override public void apply(EObject element, IModificationContext context) throws Exception {
- if (!(element instanceof Syntax)) return;
Syntax syntax = (Syntax) element;
- syntax.setName("proto2");
+ syntax.setName(proto2());
}
- });
+ };
+ String description = String.format(changeValueDescription, "syntax", quote(proto2()));
+ String label = String.format(changeValueLabel, proto2());
+ acceptor.accept(issue, label, description, ICON_FOR_CHANGE, modification);
}
@Fix(INVALID_FIELD_TAG_NUMBER_ERROR)
public void regenerateTagNumber(Issue issue, IssueResolutionAcceptor acceptor) {
- acceptor.accept(issue, regenerateTagNumberLabel, regenerateTagNumber, "field.gif", new ISemanticModification() {
+ ISemanticModification modification = new ISemanticModification() {
@Override public void apply(EObject element, IModificationContext context) throws Exception {
- if (!(element instanceof IndexedElement)) return;
IndexedElement e = (IndexedElement) element;
long tagNumber = indexedElements.calculateTagNumberOf(e);
indexedElements.setIndexTo(e, tagNumber);
}
- });
+ };
+ acceptor.accept(issue, regenerateTagNumberLabel, regenerateTagNumberDescription, "field.gif", modification);
}
@Fix(MORE_THAN_ONE_PACKAGE_ERROR)
public void removeDuplicatePackage(Issue issue, IssueResolutionAcceptor acceptor) {
- acceptor.accept(issue, removeDuplicatePackageLabel, removeDuplicatePackage, "remove.gif",
- new ISemanticModification() {
- @Override public void apply(EObject element, IModificationContext context) throws Exception {
- if (!(element instanceof Package)) return;
- Package aPackage = (Package) element;
- remove(aPackage);
- }
- });
+ final Package aPackage = element(issue, Package.class);
+ if (aPackage == null) return;
+ ISemanticModification modification = new ISemanticModification() {
+ @Override public void apply(EObject element, IModificationContext context) throws Exception {
+ if (!(element instanceof Package)) return;
+ remove(aPackage);
+ }
+ };
+ INode node = findActualNodeFor(aPackage);
+ String description = nodes.textOf(node);
+ acceptor.accept(issue, removeDuplicatePackageLabel, description, "remove.gif", modification);
+ }
+
+ @Fix(EXPECTED_BOOL_ERROR)
+ public void changeValueToTrue(Issue issue, IssueResolutionAcceptor acceptor) {
+ EObject element = elementIn(issue);
+ if (element instanceof FieldOption) {
+ FieldOption option = (FieldOption) element;
+ changeValue(option, linkTo(TRUE), CommonKeyword.TRUE, issue, acceptor);
+ }
+ }
+
+ @Fix(EXPECTED_BOOL_ERROR)
+ public void changeValueToFalse(Issue issue, IssueResolutionAcceptor acceptor) {
+ EObject element = elementIn(issue);
+ if (element instanceof FieldOption) {
+ FieldOption option = (FieldOption) element;
+ changeValue(option, linkTo(FALSE), CommonKeyword.FALSE, issue, acceptor);
+ }
+ }
+
+ // TODO rename BooleanLink to BoolLink
+ private BooleanLink linkTo(BOOL value) {
+ BooleanLink link = ProtobufFactory.eINSTANCE.createBooleanLink();
+ link.setTarget(value);
+ return link;
+ }
+
+ @Fix(EXPECTED_STRING_ERROR)
+ public void changeValueToEmptyString(Issue issue, IssueResolutionAcceptor acceptor) {
+ EObject element = elementIn(issue);
+ if (element instanceof FieldOption) {
+ FieldOption option = (FieldOption) element;
+ String valueToPropose = "";
+ changeValue(option, linkTo(valueToPropose), valueToPropose, issue, acceptor);
+ }
+ }
+
+ private StringLink linkTo(String value) {
+ StringLink link = ProtobufFactory.eINSTANCE.createStringLink();
+ link.setTarget(value);
+ return link;
+ }
+
+ private void changeValue(final FieldOption option, final Value newValue, Object proposedValue, Issue issue,
+ IssueResolutionAcceptor acceptor) {
+ ISemanticModification modification = new ISemanticModification() {
+ @Override public void apply(EObject element, IModificationContext context) throws Exception {
+ option.setValue(newValue);
+ }
+ };
+ String name = nameResolver.nameOf(option);
+ String description = String.format(changeValueDescription, name, proposedValue);
+ String label = String.format(changeValueLabel, proposedValue);
+ acceptor.accept(issue, label, description, ICON_FOR_CHANGE, modification);
+ }
+
+ private EObject elementIn(Issue issue) {
+ return element(issue, EObject.class);
+ }
+
+ private <T extends EObject> T element(final Issue issue, final Class<T> type) {
+ IModificationContext modificationContext = getModificationContextFactory().createModificationContext(issue);
+ IXtextDocument xtextDocument = modificationContext.getXtextDocument();
+ return xtextDocument.readOnly(new IUnitOfWork<T, XtextResource>() {
+ @Override public T exec(XtextResource state) throws Exception {
+ EObject e = state.getEObject(issue.getUriToProblem().fragment());
+ return (type.isInstance(e)) ? type.cast(e) : 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 d455e5d..d104050 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
@@ -88,7 +88,7 @@
target=[ComplexType|QualifiedName];
Enum:
- 'enum' name=Name '{'
+ =>'enum' name=Name '{'
elements+=EnumElement*
'}' ';'?;
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/grammar/ValidSyntax.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/grammar/ValidSyntax.java
new file mode 100644
index 0000000..c57cf8c
--- /dev/null
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/grammar/ValidSyntax.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2011 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.grammar;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public final class ValidSyntax {
+
+ public static String proto2() {
+ return "proto2";
+ }
+
+ public static boolean isProto2Syntax(String s) {
+ return proto2().equals(s);
+ }
+
+ private ValidSyntax() {}
+}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/INodes.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/INodes.java
index 20debce..95bcc68 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/INodes.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/INodes.java
@@ -110,10 +110,11 @@
/**
* Returns the text of the given node, with leading and trailing whitespace omitted.
- * @param node the given node.
- * @return the text of the given node, with leading and trailing whitespace omitted.
+ * @param node the given node, may be {@code null}.
+ * @return the text of the given node, with leading and trailing whitespace omitted.
*/
public String textOf(INode node) {
+ if (node == null) return null;
String text = node.getText();
return (text == null) ? null : text.trim();
}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/MessageFields.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/MessageFields.java
index ffcd151..16df608 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/MessageFields.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/MessageFields.java
@@ -56,6 +56,15 @@
}
/**
+ * Indicates whether the given field is of type {@code bytes}.
+ * @param field the given field.
+ * @return {@code true} if the given field is of type {@code bytes}, {@code false} otherwise.
+ */
+ public boolean isBytes(MessageField field) {
+ return isScalarType(field, BYTES);
+ }
+
+ /**
* Indicates whether the given field is of type {@code float} or {@code double}.
* @param field the given field.
* @return {@code true} if the given field is a floating point number, {@code false} otherwise.
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/naming/NameResolver.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/naming/NameResolver.java
index 45258ed..ba95742 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/naming/NameResolver.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/naming/NameResolver.java
@@ -10,22 +10,28 @@
import static org.eclipse.xtext.util.SimpleAttributeResolver.NAME_RESOLVER;
-import com.google.inject.Singleton;
-
import org.eclipse.emf.ecore.*;
+import com.google.eclipse.protobuf.grammar.CommonKeyword;
+import com.google.eclipse.protobuf.protobuf.DefaultValueFieldOption;
+import com.google.inject.Singleton;
+
/**
+ * TODO test
* @author alruiz@google.com (Alex Ruiz)
*/
@Singleton
public class NameResolver {
public String nameOf(EObject o) {
+ if (o instanceof DefaultValueFieldOption) {
+ return CommonKeyword.DEFAULT.toString();
+ }
Object value = nameFeatureOf(o);
if (value instanceof String) return (String) value;
return null;
}
-
+
private Object nameFeatureOf(EObject e) {
EStructuralFeature f = NAME_RESOLVER.getAttribute(e);
return (f != null) ? e.eGet(f) : null;
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Strings.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Strings.java
new file mode 100644
index 0000000..b909361
--- /dev/null
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Strings.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2011 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.util;
+
+/**
+ * Utility methods related to {@code String}.s
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public final class Strings {
+
+ /**
+ * Returns the given {@code String} in double quotes.
+ * @param s the given {@code String}, may be {@code null}.
+ * @return the given {@code String} in double quotes, or {@code null} if the given {@code String} is {@code null}.
+ */
+ public static String quote(String s) {
+ if (s == null) return s;
+ return "\"" + s + "\"";
+ }
+
+ private Strings() {}
+}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/DataTypeValidator.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/DataTypeValidator.java
index 4e4a82a..4d1b8bf 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/DataTypeValidator.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/DataTypeValidator.java
@@ -25,6 +25,9 @@
*/
public class DataTypeValidator extends AbstractDeclarativeValidator {
+ public static final String EXPECTED_BOOL_ERROR = "expectedBool";
+ public static final String EXPECTED_STRING_ERROR = "expectedString";
+
@Override public void register(EValidatorRegistrar registrar) {}
@Inject private FieldOptions fieldOptions;
@@ -52,7 +55,7 @@
if (!messageFields.isBool(field)) return false;
Value value = option.getValue();
if (!(value instanceof BooleanLink)) {
- error(expectedTrueOrFalse, FIELD_OPTION__VALUE);
+ error(expectedTrueOrFalse, option, FIELD_OPTION__VALUE, EXPECTED_BOOL_ERROR);
}
return true;
}
@@ -83,10 +86,10 @@
}
private boolean validateString(FieldOption option, MessageField field) {
- if (!messageFields.isString(field)) return false;
+ if (!messageFields.isBytes(field) && !messageFields.isString(field)) return false;
Value value = option.getValue();
if (!(value instanceof StringLink)) {
- error(expectedString, FIELD_OPTION__VALUE);
+ error(expectedString, option, FIELD_OPTION__VALUE, EXPECTED_STRING_ERROR);
}
return true;
}
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 911bbb4..7722c39 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
@@ -8,6 +8,7 @@
*/
package com.google.eclipse.protobuf.validation;
+import static com.google.eclipse.protobuf.grammar.ValidSyntax.isProto2Syntax;
import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.*;
import static com.google.eclipse.protobuf.validation.Messages.*;
import static java.lang.String.format;
@@ -67,7 +68,7 @@
@Check public void checkSyntaxIsProto2(Syntax syntax) {
String name = syntax.getName();
- if ("proto2".equals(name)) return;
+ if (isProto2Syntax(name)) return;
String msg = (name == null) ? expectedSyntaxIdentifier : format(unrecognizedSyntaxIdentifier, name);
error(msg, syntax, SYNTAX__NAME, SYNTAX_IS_NOT_PROTO2_ERROR);
}