blob: fe1daea19c8955108f1a5b03249f030a627af0dc [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.inspections;
import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.tree.IElementType;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.inspections.quickfix.ConvertIndentsFix;
import com.jetbrains.python.lexer.PythonIndentingLexer;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* @author yole
*/
public class PyInconsistentIndentationInspection extends PyInspection {
@Nls
@NotNull
@Override
public String getDisplayName() {
return "Inconsistent indentation";
}
@Override
public ProblemDescriptor[] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) {
if (file.getLanguage() instanceof PythonLanguage) {
return new IndentValidator(file, manager, isOnTheFly).invoke();
}
return ProblemDescriptor.EMPTY_ARRAY;
}
private static class IndentValidator {
private PsiFile myFile;
private InspectionManager myManager;
private boolean myOnTheFly;
private List<ProblemDescriptor> myProblems;
private int myLastTabs = 0;
private int myLastSpaces = 0;
public IndentValidator(PsiFile file, InspectionManager manager, boolean isOnTheFly) {
myFile = file;
myManager = manager;
myOnTheFly = isOnTheFly;
myProblems = new ArrayList<ProblemDescriptor>();
}
public ProblemDescriptor[] invoke() {
PythonIndentingLexer lexer = new PythonIndentingLexer();
final String text = myFile.getText();
lexer.start(text);
while (lexer.getTokenType() != null) {
final IElementType tokenType = lexer.getTokenType();
if (tokenType == PyTokenTypes.STATEMENT_BREAK) {
lexer.advance();
while(lexer.getTokenType() != null && lexer.getTokenType() != PyTokenTypes.LINE_BREAK) {
lexer.advance();
}
if (lexer.getTokenType() == PyTokenTypes.LINE_BREAK) {
String indent = text.substring(lexer.getTokenStart(), lexer.getTokenEnd());
validateIndent(lexer.getTokenStart(), indent);
}
}
lexer.advance();
}
return myProblems.toArray(new ProblemDescriptor[myProblems.size()]);
}
private void validateIndent(final int tokenStart, String indent) {
int lastLF = indent.lastIndexOf('\n');
String lastLineIndent = indent.substring(lastLF+1);
int spaces = 0;
int tabs = 0;
final int length = lastLineIndent.length();
for (int i = 0; i < length; i++) {
final char c = lastLineIndent.charAt(i);
if (c == ' ') spaces++;
else if (c == '\t') tabs++;
}
final int problemStart = tokenStart + lastLF + 1;
if (spaces > 0 && tabs > 0) {
reportProblem("Inconsistent indentation: mix of tabs and spaces", problemStart, length);
// don't know which one is correct => don't complain about inconsistent indentation on subsequent lines which use
// either tabs or spaces
myLastSpaces = 0;
myLastTabs = 0;
}
else {
if (spaces > 0 && myLastTabs > 0) {
reportProblem("Inconsistent indentation: previous line used tabs, this line uses spaces", problemStart, length);
}
else if (tabs > 0 && myLastSpaces > 0) {
reportProblem("Inconsistent indentation: previous line used spaces, this line uses tabs", problemStart, length);
}
if (spaces > 0 || tabs > 0) {
myLastTabs = tabs;
myLastSpaces = spaces;
}
}
}
private void reportProblem(final String descriptionTemplate, final int problemStart, final int problemLength) {
PsiElement elt = myFile.findElementAt(problemStart);
int startOffset = problemStart - elt.getTextRange().getStartOffset();
int endOffset = startOffset + problemLength;
myProblems.add(myManager.createProblemDescriptor(elt, new TextRange(startOffset, endOffset),
descriptionTemplate,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, myOnTheFly,
new ConvertIndentsFix(false), new ConvertIndentsFix(true)));
}
}
}