1 package com
.intellij
.indentation
;
3 import com
.intellij
.lang
.annotation
.AnnotationHolder
;
4 import com
.intellij
.psi
.PsiElement
;
5 import com
.intellij
.psi
.PsiElementVisitor
;
6 import com
.intellij
.psi
.PsiFile
;
7 import com
.intellij
.psi
.tree
.IElementType
;
8 import com
.intellij
.psi
.util
.PsiTreeUtil
;
9 import org
.jetbrains
.annotations
.NotNull
;
14 public class IndentationAnnotatorVisitor
extends PsiElementVisitor
{
15 protected final AnnotationHolder myHolder
;
16 private final IElementType myIndentTokenType
;
17 private final IElementType myEolTokenType
;
19 public IndentationAnnotatorVisitor(@NotNull final AnnotationHolder holder
,
20 final IElementType indentTokenType
,
21 final IElementType eolTokenType
) {
23 myIndentTokenType
= indentTokenType
;
24 myEolTokenType
= eolTokenType
;
27 @SuppressWarnings({"ConstantConditions"})
29 public void visitFile(final PsiFile file
) {
31 IndentInfo indentInfo
= null;
32 PsiElement leaf
= PsiTreeUtil
.getDeepestFirst(file
);
33 if (leaf
== null || leaf
instanceof PsiFile
){
36 PsiElement nextLeaf
= PsiTreeUtil
.nextLeaf(leaf
);
39 if (leaf
.getNode().getElementType() == myIndentTokenType
){
40 myHolder
.createErrorAnnotation(leaf
, "Indenting at the beginning of the document is illegal");
43 nextLeaf
= leaf
!= null ? PsiTreeUtil
.nextLeaf(leaf
) : null;
45 // Iterating over leafs
46 while (leaf
!= null) {
47 final IElementType leafType
= leaf
.getNode().getElementType();
48 if (leafType
== myIndentTokenType
&& nextLeaf
!=null && nextLeaf
.getNode().getElementType() != myEolTokenType
){
49 final String currentIndentText
= leaf
.getText();
50 final IndentInfo currentIndent
= getIndent(currentIndentText
);
51 final int currentIndentLength
= currentIndent
.length
;
52 // Check if spaces and tabs are mixed
53 if (currentIndentLength
== -1){
54 myHolder
.createErrorAnnotation(leaf
, "Indentation can't use both tabs and spaces");
57 if (currentIndentLength
> 0) {
58 // If we don't have indent info registered
59 if (indentInfo
== null){
60 indentInfo
= currentIndent
;
61 lastIndent
= currentIndentLength
;
63 final Boolean useTab
= indentInfo
.useTab
;
64 final int indentInfoLength
= indentInfo
.length
;
65 // If indent and current indent use different space and tabs
66 if (useTab
&& currentIndentText
.contains(" ") || !useTab
&& currentIndentText
.contains("\t")) {
67 final String message
= useTab
68 ?
"Inconsistent indentation: " + currentIndentLength
+
69 " spaces were used for indentation, but the rest of the document was indented using " +
70 indentInfoLength
+ " tabs"
71 : "Inconsistent indentation: " + currentIndentLength
+
72 " tabs were used for indentation, but the rest of the document was indented using " +
73 indentInfoLength
+ " spaces";
74 myHolder
.createErrorAnnotation(leaf
, message
);
76 // Check indent length
77 final int delta
= currentIndentLength
- lastIndent
;
78 if (currentIndentLength
% indentInfoLength
!= 0 || delta
> indentInfoLength
) {
79 final String message
= useTab
80 ? currentIndentLength
+ " tabs were used for indentation. Must be indented using " + indentInfoLength
+ " tabs"
81 : currentIndentLength
+ " spaces were used for indentation. Must be indented using " + indentInfoLength
+ " spaces";
82 myHolder
.createErrorAnnotation(leaf
, message
);
84 lastIndent
= currentIndentLength
/ indentInfoLength
* indentInfoLength
;
90 nextLeaf
= leaf
!= null ? PsiTreeUtil
.nextLeaf(leaf
) : null;
95 private class IndentInfo
{
99 private IndentInfo(final int length
, final boolean useTab
) {
100 this.length
= length
;
101 this.useTab
= useTab
;
106 private IndentInfo
getIndent(final String text
) {
107 if (text
.length() == 0){
108 return new IndentInfo(0, false);
110 if (text
.contains("\t ") || text
.contains(" \t")){
111 return new IndentInfo(-1, false);
113 return new IndentInfo(text
.length(), text
.contains("\t"));