IDEA-134372 Do not warn about scalar top-level values in JSON files (according to...
[idea/community.git] / json / src / com / intellij / json / codeinsight / JsonStandardComplianceInspection.java
1 package com.intellij.json.codeinsight;
2
3 import com.intellij.codeHighlighting.HighlightDisplayLevel;
4 import com.intellij.codeInspection.*;
5 import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
6 import com.intellij.json.JsonBundle;
7 import com.intellij.json.JsonElementTypes;
8 import com.intellij.json.psi.*;
9 import com.intellij.openapi.diagnostic.Logger;
10 import com.intellij.openapi.project.Project;
11 import com.intellij.openapi.util.text.StringUtil;
12 import com.intellij.psi.PsiComment;
13 import com.intellij.psi.PsiElement;
14 import com.intellij.psi.PsiElementVisitor;
15 import com.intellij.psi.PsiWhiteSpace;
16 import com.intellij.psi.tree.IElementType;
17 import com.intellij.psi.util.PsiTreeUtil;
18 import org.jetbrains.annotations.NotNull;
19 import org.jetbrains.annotations.Nullable;
20
21 import javax.swing.*;
22
23 /**
24  * Compliance checks include
25  * <ul>
26  * <li>Usage of line and block commentaries</li>
27  * <li>Usage of single quoted strings</li>
28  * <li>Usage of identifiers (unqouted words)</li>
29  * <li>Not double quoted string literal is used as property key</li>
30  * </ul>
31  *
32  * @author Mikhail Golubev
33  */
34 public class JsonStandardComplianceInspection extends LocalInspectionTool {
35   private static final Logger LOG = Logger.getInstance(JsonStandardComplianceInspection.class);
36
37   public boolean myWarnAboutComments = true;
38
39   @NotNull
40   public String getDisplayName() {
41     return JsonBundle.message("name.standard.compliance.inspection");
42   }
43
44   @NotNull
45   @Override
46   public HighlightDisplayLevel getDefaultLevel() {
47     return HighlightDisplayLevel.ERROR;
48   }
49
50   @NotNull
51   @Override
52   public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
53     return new JsonElementVisitor() {
54       @Override
55       public void visitComment(PsiComment comment) {
56         if (myWarnAboutComments) {
57           if (JsonStandardComplianceProvider.shouldWarnAboutComment(comment)) {
58             holder.registerProblem(comment, JsonBundle.message("msg.compliance.problem.comments"), ProblemHighlightType.WEAK_WARNING);
59           }
60         }
61       }
62
63       @Override
64       public void visitStringLiteral(@NotNull JsonStringLiteral stringLiteral) {
65         if (JsonPsiUtil.getElementTextWithoutHostEscaping(stringLiteral).startsWith("'")) {
66           holder.registerProblem(stringLiteral, JsonBundle.message("msg.compliance.problem.single.quoted.strings"),
67                                  new AddDoubleQuotesFix());
68         }
69         // May be illegal property key as well
70         super.visitStringLiteral(stringLiteral);
71       }
72
73       @Override
74       public void visitLiteral(@NotNull JsonLiteral literal) {
75         if (JsonPsiUtil.isPropertyKey(literal) && !JsonPsiUtil.getElementTextWithoutHostEscaping(literal).startsWith("\"")) {
76           holder.registerProblem(literal, JsonBundle.message("msg.compliance.problem.illegal.property.key"), new AddDoubleQuotesFix());
77         }
78       }
79
80       @Override
81       public void visitReferenceExpression(@NotNull JsonReferenceExpression reference) {
82         holder.registerProblem(reference, JsonBundle.message("msg.compliance.problem.identifier"), new AddDoubleQuotesFix());
83         // May be illegal property key as well
84         super.visitReferenceExpression(reference);
85       }
86
87       @Override
88       public void visitArray(@NotNull JsonArray array) {
89         final PsiElement trailingComma = findTrailingComma(array, JsonElementTypes.R_BRACKET);
90         if (trailingComma != null) {
91           holder.registerProblem(trailingComma, JsonBundle.message("msg.compliance.problem.trailing.comma"));
92         }
93       }
94
95       @Override
96       public void visitObject(@NotNull JsonObject object) {
97         final PsiElement trailingComma = findTrailingComma(object, JsonElementTypes.R_CURLY);
98         if (trailingComma != null) {
99           holder.registerProblem(trailingComma, JsonBundle.message("msg.compliance.problem.trailing.comma"));
100         }
101       }
102     };
103   }
104
105   @Nullable
106   private static PsiElement findTrailingComma(@NotNull JsonContainer container, @NotNull IElementType ending) {
107     final PsiElement lastChild = container.getLastChild();
108     if (lastChild.getNode().getElementType() != ending) {
109       return null;
110     }
111     final PsiElement beforeEnding = PsiTreeUtil.skipSiblingsBackward(lastChild, PsiComment.class, PsiWhiteSpace.class);
112     if (beforeEnding != null && beforeEnding.getNode().getElementType() == JsonElementTypes.COMMA) {
113       return beforeEnding;
114     }
115     return null;
116   }
117
118
119   @Override
120   public JComponent createOptionsPanel() {
121     final MultipleCheckboxOptionsPanel optionsPanel = new MultipleCheckboxOptionsPanel(this);
122     optionsPanel.addCheckbox(JsonBundle.message("option.warn.about.comments.name"), "myWarnAboutComments");
123     return optionsPanel;
124   }
125
126   private static class AddDoubleQuotesFix implements LocalQuickFix {
127     @NotNull
128     @Override
129     public String getName() {
130       return JsonBundle.message("name.add.double.quotes.quickfix");
131     }
132
133     @NotNull
134     @Override
135     public String getFamilyName() {
136       return getName();
137     }
138
139     @Override
140     public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
141       final PsiElement element = descriptor.getPsiElement();
142       if (element instanceof JsonLiteral || element instanceof JsonReferenceExpression) {
143         final String content = StringUtil.stripQuotesAroundValue(element.getText());
144         element.replace(new JsonElementGenerator(project).createStringLiteral(content));
145       }
146       else if (element != null) {
147         LOG.error("Quick fix was applied to unexpected element", element.getText(), element.getParent().getText());
148       }
149     }
150   }
151 }