package com.jetbrains.lang.dart.ide.annotator;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInspection.SuppressIntentionAction;
import com.intellij.codeInspection.SuppressableProblemGroup;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
public class DartProblemGroup implements SuppressableProblemGroup {
@NotNull private String myErrorCode;
@NotNull private String myErrorSeverity;
public DartProblemGroup(@NotNull final String errorCode, @NotNull final String errorSeverity) {
myErrorCode = errorCode;
myErrorSeverity = errorSeverity;
}
@NotNull
@Override
public SuppressIntentionAction[] getSuppressActions(@Nullable final PsiElement element) {
return new SuppressIntentionAction[]{
new DartSuppressAction(myErrorCode, myErrorSeverity, false),
new DartSuppressAction(myErrorCode, myErrorSeverity, true)
};
}
@Nullable
@Override
public String getProblemName() {
return null;
}
public static class DartSuppressAction extends SuppressIntentionAction implements Comparable<IntentionAction> {
public static final String IGNORE_PREFIX = "ignore:";
@NotNull private final String myErrorCode;
private final boolean myEolComment;
/**
* @param eolComment <code>true</code> means that <code>//ignore</code> comment should be placed in the end of the current line, <code>false</code> -> on previous line
*/
public DartSuppressAction(@NotNull final String errorCode, @NotNull final String errorSeverity, boolean eolComment) {
myErrorCode = errorCode;
myEolComment = eolComment;
setText("Suppress " + errorSeverity.toLowerCase(Locale.US) + (eolComment ? " with EOL comment" : " with comment"));
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return "Suppress errors and warnings in Dart code";
}
@Override
public int compareTo(final IntentionAction o) {
if (o instanceof DartSuppressAction) {
return ((DartSuppressAction)o).myEolComment ? -1 : 1;
}
return 0;
}
@Override
public boolean isAvailable(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) {
if (editor == null) return false;
final Document document = editor.getDocument();
final int line = document.getLineNumber(element.getTextRange().getStartOffset());
// if //ignore: comment is already there then suggest to update it, but do not suggest to add another one
if (myEolComment) {
return !hasIgnoreCommentOnPrevLine(document, line) || hasEolIgnoreComment(document, line);
}
else {
return !hasEolIgnoreComment(document, line) || hasIgnoreCommentOnPrevLine(document, line);
}
}
private static boolean hasEolIgnoreComment(@NotNull final Document document, final int line) {
final CharSequence lineText =
document.getCharsSequence().subSequence(document.getLineStartOffset(line), document.getLineEndOffset(line));
if (!StringUtil.contains(lineText, IGNORE_PREFIX)) return false;
int index = lineText.toString().lastIndexOf(IGNORE_PREFIX);
while (index > 0) {
char ch = lineText.charAt(--index);
switch (ch) {
case ' ':
continue;
case '/':
return index >= 2 && lineText.charAt(index - 1) == '/' && lineText.charAt(index - 2) != '/';
default:
return false;
}
}
return false;
}
private static boolean hasIgnoreCommentOnPrevLine(@NotNull final Document document, final int line) {
if (line == 0) return false;
final CharSequence prevLine =
document.getCharsSequence().subSequence(document.getLineStartOffset(line - 1), document.getLineEndOffset(line - 1));
int index = -1;
while (++index < prevLine.length()) {
char ch = prevLine.charAt(index);
switch (ch) {
case ' ':
continue;
case '/':
if (prevLine.length() > index + 1 && prevLine.charAt(index + 1) == '/') {
final String comment = prevLine.subSequence(index + 2, prevLine.length()).toString();
if (StringUtil.trimLeading(comment, ' ').startsWith(IGNORE_PREFIX)) {
return true;
}
}
return false;
default:
return false;
}
}
return false;
}
@Override
public void invoke(@NotNull final Project project,
final Editor editor,
@NotNull final PsiElement element) throws IncorrectOperationException {
if (editor == null) return;
final Document document = editor.getDocument();
final int line = document.getLineNumber(element.getTextRange().getStartOffset());
if (myEolComment) {
if (hasEolIgnoreComment(document, line)) {
appendErrorCode(document, line, myErrorCode);
}
else {
addEolComment(document, line, myErrorCode);
}
}
else {
if (hasIgnoreCommentOnPrevLine(document, line)) {
appendErrorCode(document, line - 1, myErrorCode);
}
else {
addCommentOnPrevLine(document, line, myErrorCode);
}
}
}
private static void appendErrorCode(@NotNull final Document document, final int line, @NotNull final String errorCode) {
final int lineEndOffset = document.getLineEndOffset(line);
int index = lineEndOffset - 1;
while (index >= 0 && document.getCharsSequence().charAt(index) == ' ') {
index--;
}
document.replaceString(index + 1, lineEndOffset, ", " + errorCode);
}
private static void addEolComment(@NotNull final Document document, final int line, @NotNull final String errorCode) {
final int lineStartOffset = document.getLineStartOffset(line);
final int lineEndOffset = document.getLineEndOffset(line);
final CharSequence lineText = document.getCharsSequence().subSequence(lineStartOffset, lineEndOffset);
final int commentIndex = StringUtil.indexOf(lineText, "//");
if (commentIndex >= 0) {
// before existing comment
document.insertString(lineStartOffset + commentIndex, "// ignore: " + errorCode + ", ");
}
else {
int index = lineEndOffset - 1;
while (index >= 0 && document.getCharsSequence().charAt(index) == ' ') {
index--;
}
document.replaceString(index + 1, lineEndOffset, " // ignore: " + errorCode);
}
}
private static void addCommentOnPrevLine(@NotNull final Document document, final int line, @NotNull final String errorCode) {
final int lineStartOffset = document.getLineStartOffset(line);
int offset = 0;
while (document.getCharsSequence().charAt(lineStartOffset + offset) == ' ') {
offset++;
}
document.insertString(lineStartOffset, StringUtil.repeat(" ", offset) + "// ignore: " + errorCode + "\n");
}
}
}