Skip to content

Commit 6db6dd2

Browse files
Merge pull request #23 from protostuff/feature/check-tag-numbers
Validate message fields, enum constants and service rpc methods
2 parents 652c54d + ad92b66 commit 6db6dd2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+987
-120
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ gradle-app.setting
1212

1313
.idea/
1414
*.iml
15+
classes/

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ dependencies {
2424
compile 'org.antlr:antlr4-runtime:4.5.1'
2525
compile 'org.antlr:antlr4-jetbrains-adapter:1.0.0'
2626
compile 'com.google.guava:guava:19.0'
27-
compile 'io.protostuff:protostuff-parser:2.0.0-alpha21'
27+
compile 'io.protostuff:protostuff-parser:2.0.0-alpha25'
2828
}
2929

3030
apply plugin: 'idea'

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Available idea versions:
22
# https://www.jetbrains.com/intellij-repository/releases
33
# https://www.jetbrains.com/intellij-repository/snapshots
4-
version=0.4.1
4+
version=0.5.0
55
ideaVersion=145.258.11
66
# https://intellij-support.jetbrains.com/hc/en-us/articles/206544879-Selecting-the-JDK-version-the-IDE-will-run-under
77
# Java 8 is required to run IntelliJ IDEA starting from version 16

src/main/java/io/protostuff/jetbrains/plugin/ProtoParserDefinition.java

Lines changed: 50 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import com.intellij.lexer.Lexer;
77
import com.intellij.openapi.project.Project;
88
import com.intellij.psi.FileViewProvider;
9-
import com.intellij.psi.PsiElement;
109
import com.intellij.psi.PsiFile;
1110
import com.intellij.psi.tree.IElementType;
1211
import com.intellij.psi.tree.IFileElementType;
@@ -24,7 +23,10 @@
2423
import org.antlr.v4.runtime.tree.ParseTree;
2524
import org.jetbrains.annotations.NotNull;
2625

26+
import java.util.HashMap;
2727
import java.util.List;
28+
import java.util.Map;
29+
import java.util.function.Function;
2830

2931
import static io.protostuff.compiler.parser.ProtoLexer.*;
3032

@@ -193,6 +195,8 @@ public class ProtoParserDefinition implements ParserDefinition {
193195
public static final IElementType R_TYPE_REFERENCE = RULE_TYPES.get(ProtoParser.RULE_typeReference);
194196
public static final IElementType R_NAME = RULE_TYPES.get(ProtoParser.RULE_ident);
195197
public static final IElementType R_FIELD_MODIFIER = RULE_TYPES.get(ProtoParser.RULE_fieldModifier);
198+
public static final IElementType R_FIELD_NAME = RULE_TYPES.get(ProtoParser.RULE_fieldName);
199+
public static final IElementType R_TAG = RULE_TYPES.get(ProtoParser.RULE_tag);
196200
private static final IFileElementType FILE = new IFileElementType(ProtoLanguage.INSTANCE);
197201
private static final TokenSet COMMENTS = PSIElementTypeFactory.createTokenSet(ProtoLanguage.INSTANCE, COMMENT, LINE_COMMENT);
198202
public static final TokenSet WHITESPACE = PSIElementTypeFactory.createTokenSet(ProtoLanguage.INSTANCE, WS, NL);
@@ -207,6 +211,45 @@ public static RuleIElementType rule(int rule) {
207211
return RULE_TYPES.get(rule);
208212
}
209213

214+
private final Map<Integer, Function<ASTNode, ANTLRPsiNode>> elementFactories = new HashMap<>();
215+
216+
public ProtoParserDefinition() {
217+
register(ProtoParser.RULE_syntax, SyntaxNode::new);
218+
register(ProtoParser.RULE_packageStatement, PackageStatement::new);
219+
register(ProtoParser.RULE_importStatement, ImportNode::new);
220+
register(ProtoParser.RULE_fileReference, FileReferenceNode::new);
221+
register(ProtoParser.RULE_messageBlock, MessageNode::new);
222+
register(ProtoParser.RULE_messageName, MessageNameNode::new);
223+
register(ProtoParser.RULE_field, FieldNode::new);
224+
register(ProtoParser.RULE_typeReference, TypeReferenceNode::new);
225+
register(ProtoParser.RULE_groupBlock, GroupNode::new);
226+
register(ProtoParser.RULE_enumBlock, EnumNode::new);
227+
register(ProtoParser.RULE_enumField, EnumConstantNode::new);
228+
register(ProtoParser.RULE_serviceBlock, ServiceNode::new);
229+
register(ProtoParser.RULE_rpcMethod, RpcMethodNode::new);
230+
register(ProtoParser.RULE_optionEntry, OptionEntryNode::new);
231+
register(ProtoParser.RULE_option, OptionNode::new);
232+
register(ProtoParser.RULE_oneof, OneOfNode::new);
233+
register(ProtoParser.RULE_oneofField, OneofFieldNode::new);
234+
register(ProtoParser.RULE_extendBlock, ExtendNode::new);
235+
register(ProtoParser.RULE_extensions, ExtensionsNode::new);
236+
register(ProtoParser.RULE_map, MapNode::new);
237+
register(ProtoParser.RULE_mapKey, MapKeyNode::new);
238+
register(ProtoParser.RULE_optionValue, OptionValueNode::new);
239+
register(ProtoParser.RULE_range, RangeNode::new);
240+
register(ProtoParser.RULE_reservedFieldRanges, ReservedFieldRangesNode::new);
241+
register(ProtoParser.RULE_reservedFieldNames, ReservedFieldNamesNode::new);
242+
register(ProtoParser.RULE_rpcType, RpcMethodTypeNode::new);
243+
register(ProtoParser.RULE_proto, ProtoRootNode::new);
244+
}
245+
246+
private void register(int rule, Function<ASTNode, ANTLRPsiNode> factory) {
247+
if (elementFactories.containsKey(rule)) {
248+
throw new IllegalStateException("Duplicate rule");
249+
}
250+
elementFactories.put(rule, factory);
251+
}
252+
210253
@NotNull
211254
@Override
212255
public Lexer createLexer(Project project) {
@@ -256,7 +299,7 @@ public TokenSet getStringLiteralElements() {
256299

257300
@NotNull
258301
@Override
259-
public PsiElement createElement(ASTNode node) {
302+
public ANTLRPsiNode createElement(ASTNode node) {
260303
IElementType elType = node.getElementType();
261304
if (elType instanceof TokenIElementType) {
262305
return new ANTLRPsiNode(node);
@@ -265,58 +308,12 @@ public PsiElement createElement(ASTNode node) {
265308
return new ANTLRPsiNode(node);
266309
}
267310
RuleIElementType ruleElType = (RuleIElementType) elType;
268-
switch (ruleElType.getRuleIndex()) {
269-
case ProtoParser.RULE_syntax:
270-
return new SyntaxNode(node);
271-
case ProtoParser.RULE_packageStatement:
272-
return new PackageStatement(node);
273-
case ProtoParser.RULE_importStatement:
274-
return new ImportNode(node);
275-
case ProtoParser.RULE_fileReference:
276-
return new FileReferenceNode(node);
277-
case ProtoParser.RULE_messageBlock:
278-
return new MessageNode(node);
279-
case ProtoParser.RULE_messageName:
280-
return new MessageNameNode(node);
281-
case ProtoParser.RULE_field:
282-
return new FieldNode(node);
283-
case ProtoParser.RULE_typeReference:
284-
return new TypeReferenceNode(node);
285-
case ProtoParser.RULE_groupBlock:
286-
return new GroupNode(node);
287-
case ProtoParser.RULE_enumBlock:
288-
return new EnumNode(node);
289-
case ProtoParser.RULE_enumField:
290-
return new EnumConstantNode(node);
291-
case ProtoParser.RULE_serviceBlock:
292-
return new ServiceNode(node);
293-
case ProtoParser.RULE_rpcMethod:
294-
return new RpcMethodNode(node);
295-
case ProtoParser.RULE_optionEntry:
296-
return new OptionNode(node);
297-
case ProtoParser.RULE_oneof:
298-
return new OneOfNode(node);
299-
case ProtoParser.RULE_extendBlock:
300-
return new ExtendNode(node);
301-
case ProtoParser.RULE_extensions:
302-
return new ExtensionsNode(node);
303-
case ProtoParser.RULE_map:
304-
return new MapNode(node);
305-
case ProtoParser.RULE_mapKey:
306-
return new MapKeyNode(node);
307-
case ProtoParser.RULE_optionValue:
308-
return new OptionValueNode(node);
309-
case ProtoParser.RULE_range:
310-
return new RangeNode(node);
311-
case ProtoParser.RULE_reserved:
312-
return new ReservedFieldsNode(node);
313-
case ProtoParser.RULE_rpcType:
314-
return new RpcMethodTypeNode(node);
315-
case ProtoParser.RULE_proto:
316-
return new ProtoRootNode(node);
317-
default:
318-
return new ANTLRPsiNode(node);
311+
int ruleIndex = ruleElType.getRuleIndex();
312+
if (elementFactories.containsKey(ruleIndex)) {
313+
Function<ASTNode, ANTLRPsiNode> factory = elementFactories.get(ruleIndex);
314+
return factory.apply(node);
319315
}
316+
return new ANTLRPsiNode(node);
320317
}
321318

322319
@Override
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package io.protostuff.jetbrains.plugin.annotator;
2+
3+
import com.google.common.annotations.VisibleForTesting;
4+
import com.intellij.codeInspection.ProblemHighlightType;
5+
import com.intellij.lang.ASTNode;
6+
import com.intellij.lang.annotation.Annotation;
7+
import com.intellij.lang.annotation.AnnotationHolder;
8+
import com.intellij.lang.annotation.Annotator;
9+
import com.intellij.psi.PsiElement;
10+
import com.intellij.psi.PsiErrorElement;
11+
import com.intellij.psi.PsiRecursiveElementVisitor;
12+
import io.protostuff.compiler.model.Field;
13+
import io.protostuff.jetbrains.plugin.psi.*;
14+
import org.jetbrains.annotations.NotNull;
15+
import org.jetbrains.annotations.Nullable;
16+
17+
import java.util.*;
18+
19+
import static io.protostuff.jetbrains.plugin.ProtostuffBundle.message;
20+
21+
/**
22+
* @author Kostiantyn Shchepanovskyi
23+
*/
24+
public class ProtoErrorsAnnotator implements Annotator {
25+
26+
private final int MIN_TAG = 1;
27+
private final int MAX_TAG = Field.MAX_TAG_VALUE;
28+
private final int SYS_RESERVED_START = 19000;
29+
private final int SYS_RESERVED_END = 19999;
30+
31+
private AnnotationHolder holder;
32+
33+
@Override
34+
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
35+
boolean check = hasErrors(element);
36+
if (!check) {
37+
this.holder = holder;
38+
if (element instanceof MessageNode) {
39+
MessageNode message = (MessageNode) element;
40+
Collection<MessageField> fields = message.getFields();
41+
checkInvalidFieldTags(fields);
42+
checkDuplicateFieldTags(fields);
43+
checkDuplicateFieldNames(fields);
44+
checkReservedFieldTags(message, fields);
45+
checkReservedFieldNames(message, fields);
46+
} else if (element instanceof EnumNode) {
47+
EnumNode anEnum = (EnumNode) element;
48+
List<EnumConstantNode> constants = anEnum.getConstants();
49+
checkDuplicateEnumConstantNames(constants);
50+
checkDuplicateEnumConstantValues(anEnum, constants);
51+
} else if (element instanceof ServiceNode) {
52+
ServiceNode service = (ServiceNode) element;
53+
List<RpcMethodNode> rpcMethods = service.getRpcMethods();
54+
checkDuplicateServiceMethodNames(rpcMethods);
55+
}
56+
this.holder = null;
57+
}
58+
}
59+
60+
private void checkDuplicateServiceMethodNames(List<RpcMethodNode> rpcMethods) {
61+
Map<String, RpcMethodNode> methodByName = new HashMap<>();
62+
for (RpcMethodNode methods : rpcMethods) {
63+
String name = methods.getMethodName();
64+
if (methodByName.containsKey(name)) {
65+
String message = message("error.duplicate.method.name", name);
66+
markError(methods.getNode(), methodByName.get(name).getMethodNameNode(), message);
67+
markError(methods.getNode(), methods.getMethodNameNode(), message);
68+
}
69+
methodByName.put(name, methods);
70+
}
71+
}
72+
73+
private void checkDuplicateEnumConstantValues(EnumNode anEnum, List<EnumConstantNode> constants) {
74+
if (anEnum.allowAlias()) {
75+
return;
76+
}
77+
Map<Integer, EnumConstantNode> fieldByTag = new HashMap<>();
78+
for (EnumConstantNode constant : constants) {
79+
int tag = constant.getConstantValue();
80+
if (fieldByTag.containsKey(tag)) {
81+
String message = message("error.duplicate.constant.value", tag);
82+
markError(constant.getNode(), fieldByTag.get(tag).getConstantValueNode(), message);
83+
markError(constant.getNode(), constant.getConstantValueNode(), message);
84+
}
85+
fieldByTag.put(tag, constant);
86+
}
87+
}
88+
89+
private void checkDuplicateEnumConstantNames(List<EnumConstantNode> constants) {
90+
Map<String, EnumConstantNode> fieldByName = new HashMap<>();
91+
for (EnumConstantNode constant : constants) {
92+
String name = constant.getConstantName();
93+
if (fieldByName.containsKey(name)) {
94+
String message = message("error.duplicate.constant.name", name);
95+
markError(constant.getNode(), fieldByName.get(name).getConstantNameNode(), message);
96+
markError(constant.getNode(), constant.getConstantNameNode(), message);
97+
}
98+
fieldByName.put(name, constant);
99+
}
100+
}
101+
102+
private boolean hasErrors(PsiElement element) {
103+
if (element instanceof AntlrParserRuleNode) {
104+
AntlrParserRuleNode node = (AntlrParserRuleNode) element;
105+
return node.hasSyntaxErrors();
106+
} else {
107+
final boolean[] hasErrors = {false};
108+
new PsiRecursiveElementVisitor() {
109+
@Override
110+
public void visitErrorElement(PsiErrorElement element) {
111+
hasErrors[0] = true;
112+
}
113+
};
114+
return hasErrors[0];
115+
}
116+
}
117+
118+
private void checkReservedFieldTags(MessageNode message, Collection<MessageField> fields) {
119+
List<RangeNode> ranges = message.getReservedFieldRanges();
120+
for (MessageField field : fields) {
121+
int tag = field.getTag();
122+
for (RangeNode range : ranges) {
123+
if (range.contains(tag)) {
124+
markError(field.getNode(), field.getTagNode(), message("error.reserved.tag.value", tag));
125+
}
126+
}
127+
}
128+
}
129+
130+
private void checkReservedFieldNames(MessageNode message, Collection<MessageField> fields) {
131+
Set<String> names = message.getReservedFieldNames();
132+
for (MessageField field : fields) {
133+
String name = field.getFieldName();
134+
if (names.contains(name)) {
135+
markError(field.getNode(), field.getFieldNameNode(), message("error.reserved.field.name", name));
136+
}
137+
}
138+
}
139+
140+
private void checkInvalidFieldTags(Collection<MessageField> fields) {
141+
for (MessageField field : fields) {
142+
int tag = field.getTag();
143+
if (!isValidTagValue(tag)) {
144+
markError(field.getNode(), field.getTagNode(), message("error.invalid.tag.not.in.range",
145+
tag, MIN_TAG, SYS_RESERVED_START, SYS_RESERVED_END, MAX_TAG));
146+
}
147+
}
148+
}
149+
150+
private void markError(ASTNode parent, @Nullable ASTNode node, String message) {
151+
Annotation annotation;
152+
if (node == null) {
153+
annotation = holder.createErrorAnnotation(parent, message);
154+
} else {
155+
annotation = holder.createErrorAnnotation(node, message);
156+
}
157+
annotation.setHighlightType(ProblemHighlightType.GENERIC_ERROR);
158+
}
159+
160+
@VisibleForTesting
161+
boolean isValidTagValue(int tag) {
162+
return tag >= MIN_TAG && tag <= MAX_TAG
163+
&& !(tag >= SYS_RESERVED_START && tag <= SYS_RESERVED_END);
164+
}
165+
166+
private void checkDuplicateFieldTags(Collection<MessageField> fields) {
167+
Map<Integer, MessageField> fieldByTag = new HashMap<>();
168+
for (MessageField field : fields) {
169+
int tag = field.getTag();
170+
if (fieldByTag.containsKey(tag)) {
171+
String message = message("error.duplicate.field.tag", tag);
172+
markError(field.getNode(), fieldByTag.get(tag).getTagNode(), message);
173+
markError(field.getNode(), field.getTagNode(), message);
174+
}
175+
fieldByTag.put(tag, field);
176+
}
177+
}
178+
179+
private void checkDuplicateFieldNames(Collection<MessageField> fields) {
180+
Map<String, MessageField> fieldByName = new HashMap<>();
181+
for (MessageField field : fields) {
182+
String name = field.getFieldName();
183+
if (fieldByName.containsKey(name)) {
184+
String message = message("error.duplicate.field.name", name);
185+
markError(field.getNode(), fieldByName.get(name).getFieldNameNode(), message);
186+
markError(field.getNode(), field.getFieldNameNode(), message);
187+
}
188+
fieldByName.put(name, field);
189+
}
190+
}
191+
}

src/main/java/io/protostuff/jetbrains/plugin/formatter/BlockFactory.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,9 @@ class BlockFactory {
3939
register(rule(RULE_optionValue), LeafBlock::new);
4040
register(rule(RULE_fieldModifier), LeafBlock::new);
4141
register(rule(RULE_typeReference), LeafBlock::new);
42-
register(rule(RULE_ranges), StatementBlock::new);
4342
register(rule(RULE_range), StatementBlock::new);
44-
register(rule(RULE_reserved), StatementBlock::new);
45-
register(rule(RULE_fieldNames), StatementBlock::new);
43+
register(rule(RULE_reservedFieldNames), StatementBlock::new);
44+
register(rule(RULE_reservedFieldRanges), StatementBlock::new);
4645
register(rule(RULE_fieldOptions), StatementBlock::new);
4746
register(rule(RULE_map), StatementBlock::new);
4847
register(rule(RULE_syntax), StatementBlock::new);
@@ -68,14 +67,17 @@ class BlockFactory {
6867

6968
register(rule(RULE_enumName), LeafBlock::new);
7069
register(rule(RULE_enumFieldName), LeafBlock::new);
70+
register(rule(RULE_enumFieldValue), LeafBlock::new);
7171
register(rule(RULE_serviceName), LeafBlock::new);
7272
register(rule(RULE_rpcName), LeafBlock::new);
7373
register(rule(RULE_messageName), LeafBlock::new);
7474
register(rule(RULE_oneofName), LeafBlock::new);
7575
register(rule(RULE_groupName), LeafBlock::new);
76-
register(rule(RULE_fieldNameString), LeafBlock::new);
7776
register(rule(RULE_fullIdent), LeafBlock::new);
7877
register(rule(RULE_fileReference), LeafBlock::new);
78+
register(rule(RULE_rangeFrom), LeafBlock::new);
79+
register(rule(RULE_rangeTo), LeafBlock::new);
80+
register(rule(RULE_reservedFieldName), LeafBlock::new);
7981
}
8082

8183
private static void register(IElementType elementType, Factory factory) {

0 commit comments

Comments
 (0)