Skip to content

Commit 015ba09

Browse files
#39: Validate field labels for proto2 and proto3
1 parent ca58806 commit 015ba09

File tree

11 files changed

+151
-0
lines changed

11 files changed

+151
-0
lines changed

src/main/java/io/protostuff/jetbrains/plugin/annotator/ProtoErrorsAnnotator.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import io.protostuff.jetbrains.plugin.psi.AntlrParserRuleNode;
1414
import io.protostuff.jetbrains.plugin.psi.EnumConstantNode;
1515
import io.protostuff.jetbrains.plugin.psi.EnumNode;
16+
import io.protostuff.jetbrains.plugin.psi.FieldLabel;
17+
import io.protostuff.jetbrains.plugin.psi.FieldNode;
1618
import io.protostuff.jetbrains.plugin.psi.MessageField;
1719
import io.protostuff.jetbrains.plugin.psi.MessageNode;
1820
import io.protostuff.jetbrains.plugin.psi.ProtoRootNode;
@@ -24,6 +26,7 @@
2426
import java.util.HashMap;
2527
import java.util.List;
2628
import java.util.Map;
29+
import java.util.Optional;
2730
import java.util.Set;
2831
import org.jetbrains.annotations.NotNull;
2932
import org.jetbrains.annotations.Nullable;
@@ -58,6 +61,8 @@ public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder hold
5861
checkDuplicateFieldNames(fields);
5962
checkReservedFieldTags(message, fields);
6063
checkReservedFieldNames(message, fields);
64+
} else if (element instanceof FieldNode) {
65+
checkFieldLabel((FieldNode) element, syntax);
6166
} else if (element instanceof EnumNode) {
6267
EnumNode anEnum = (EnumNode) element;
6368
List<EnumConstantNode> constants = anEnum.getConstants();
@@ -73,6 +78,38 @@ public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder hold
7378
}
7479
}
7580

81+
private void checkFieldLabel(FieldNode field, Syntax syntax) {
82+
switch (syntax) {
83+
case PROTO2:
84+
checkFieldLabelProto2(field);
85+
break;
86+
case PROTO3:
87+
checkFieldLabelProto3(field);
88+
break;
89+
default:
90+
throw new IllegalStateException(String.valueOf(syntax));
91+
}
92+
}
93+
94+
private void checkFieldLabelProto2(FieldNode field) {
95+
ASTNode fieldLabelNode = field.getFieldLabelNode();
96+
if (fieldLabelNode == null) {
97+
String message = message("error.missing.field.label");
98+
markError(field.getNode(), null, message);
99+
}
100+
}
101+
102+
private void checkFieldLabelProto3(FieldNode field) {
103+
Optional<FieldLabel> fieldLabel = field.getFieldLabel();
104+
fieldLabel.ifPresent(label -> {
105+
if (label == FieldLabel.OPTIONAL
106+
|| label == FieldLabel.REQUIRED) {
107+
String message = message("error.illegal.field.label", label.getName());
108+
markError(field.getFieldLabelNode(), null, message);
109+
}
110+
});
111+
}
112+
76113
private ProtoRootNode getProtoRoot(PsiElement element) {
77114
PsiElement tmp = element;
78115
while (tmp != null && !(tmp instanceof ProtoRootNode)) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.protostuff.jetbrains.plugin.psi;
2+
3+
import java.util.Optional;
4+
5+
/**
6+
* Field label.
7+
*
8+
* @author Kostiantyn Shchepanovskyi
9+
*/
10+
public enum FieldLabel {
11+
OPTIONAL("optional"),
12+
REQUIRED("required"),
13+
REPEATED("repeated");
14+
15+
private final String name;
16+
17+
FieldLabel(String name) {
18+
this.name = name;
19+
}
20+
21+
public String getName() {
22+
return name;
23+
}
24+
25+
/**
26+
* Get field label by name.
27+
*/
28+
public static Optional<FieldLabel> forString(String name) {
29+
for (FieldLabel fieldLabel : FieldLabel.values()) {
30+
if (fieldLabel.getName().equals(name)) {
31+
return Optional.of(fieldLabel);
32+
}
33+
}
34+
return Optional.empty();
35+
}
36+
}

src/main/java/io/protostuff/jetbrains/plugin/psi/FieldNode.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import io.protostuff.jetbrains.plugin.ProtoParserDefinition;
1313
import java.util.Collection;
1414
import java.util.Collections;
15+
import java.util.Optional;
1516
import org.antlr.jetbrains.adapter.psi.IdentifierDefSubtree;
1617
import org.jetbrains.annotations.NotNull;
1718

@@ -70,6 +71,21 @@ public ASTNode getTagNode() {
7071
return node.findChildByType(R_TAG);
7172
}
7273

74+
@Override
75+
public Optional<FieldLabel> getFieldLabel() {
76+
ASTNode label = getFieldLabelNode();
77+
if (label == null) {
78+
return Optional.empty();
79+
}
80+
return FieldLabel.forString(label.getText());
81+
}
82+
83+
@Override
84+
public ASTNode getFieldLabelNode() {
85+
ASTNode node = getNode();
86+
return node.findChildByType(R_FIELD_MODIFIER);
87+
}
88+
7389
@Override
7490
public String toString() {
7591
return "FieldNode(" + getFieldName() + "=" + getTag() + ")";

src/main/java/io/protostuff/jetbrains/plugin/psi/MapNode.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static io.protostuff.jetbrains.plugin.psi.Util.decodeIntegerFromText;
66

77
import com.intellij.lang.ASTNode;
8+
import java.util.Optional;
89
import org.antlr.jetbrains.adapter.psi.AntlrPsiNode;
910
import org.jetbrains.annotations.NotNull;
1011

@@ -54,4 +55,14 @@ public TypeReferenceNode getFieldType() {
5455
return null;
5556
}
5657

58+
@Override
59+
public Optional<FieldLabel> getFieldLabel() {
60+
return Optional.empty();
61+
}
62+
63+
@Override
64+
public ASTNode getFieldLabelNode() {
65+
return null;
66+
}
67+
5768
}

src/main/java/io/protostuff/jetbrains/plugin/psi/MessageField.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.intellij.lang.ASTNode;
44
import com.intellij.psi.PsiElement;
5+
import java.util.Optional;
56

67
/**
78
* Message field node.
@@ -18,5 +19,9 @@ public interface MessageField extends PsiElement {
1819

1920
ASTNode getTagNode();
2021

22+
Optional<FieldLabel> getFieldLabel();
23+
24+
ASTNode getFieldLabelNode();
25+
2126
TypeReferenceNode getFieldType();
2227
}

src/main/java/io/protostuff/jetbrains/plugin/psi/OneofFieldNode.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import io.protostuff.jetbrains.plugin.ProtoParserDefinition;
1313
import java.util.Collection;
1414
import java.util.Collections;
15+
import java.util.Optional;
1516
import org.antlr.jetbrains.adapter.psi.IdentifierDefSubtree;
1617
import org.jetbrains.annotations.NotNull;
1718

@@ -66,6 +67,16 @@ public ASTNode getTagNode() {
6667
return node.findChildByType(R_TAG);
6768
}
6869

70+
@Override
71+
public Optional<FieldLabel> getFieldLabel() {
72+
return Optional.empty();
73+
}
74+
75+
@Override
76+
public ASTNode getFieldLabelNode() {
77+
return null;
78+
}
79+
6980
@Override
7081
public TypeReferenceNode getFieldType() {
7182
return findChildByClass(TypeReferenceNode.class);

src/main/resources/io/protostuff/protobuf-jetbrains-plugin/messages/ProtostuffBundle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ error.reserved.field.name=Reserved field name: {0}
1818
error.duplicate.constant.name=Duplicate constant name: {0}
1919
error.duplicate.constant.value=Duplicate constant value: {0}
2020
error.duplicate.method.name=Duplicate RPC method name: {0}
21+
error.missing.field.label=Missing field label
22+
error.illegal.field.label=Field label "{0}" is not allowed in proto3
2123
element.context.display={0} in {1}

src/test/java/io/protostuff/jetbrains/plugin/annotator/ProtoErrorsAnnotatorTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ public void testDuplicateServiceMethodName() {
6666
check();
6767
}
6868

69+
public void testProto2MissingFieldLabel() {
70+
check();
71+
}
72+
73+
public void testProto3IllegalOptionalFieldLabel() {
74+
check();
75+
}
76+
77+
public void testProto3IllegalRequiredFieldLabel() {
78+
check();
79+
}
80+
6981
private void check() {
7082
String file = getTestName(false) + ".proto";
7183
myFixture.configureByFiles(file);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
syntax = "proto2";
2+
3+
package annotator;
4+
5+
message TestMessage {
6+
<error descr="Missing field label">int32 field = 1;</error>
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
syntax = "proto3";
2+
3+
package annotator;
4+
5+
message TestMessage {
6+
<error descr="Field label \"optional\" is not allowed in proto3">optional</error> int32 field = 1;
7+
}

0 commit comments

Comments
 (0)