RegExp: new "Begin or end anchor in unexpected position" inspection

This commit is contained in:
Bas Leijdekkers
2018-01-03 17:51:13 +01:00
parent d4414dc4ee
commit 77426f343a
4 changed files with 158 additions and 0 deletions
@@ -54,5 +54,8 @@
<localInspection groupName="RegExp" language="RegExp" shortName="RegExpRedundantEscape"
displayName="Redundant character escape" enabledByDefault="true" level="WARNING"
implementationClass="org.intellij.lang.regexp.inspection.RedundantEscapeInspection"/>
<localInspection groupName="RegExp" language="RegExp" shortName="RegExpUnexpectedAnchor"
displayName="Begin or end anchor in unexpected position" enabledByDefault="true" level="WARNING"
implementationClass="org.intellij.lang.regexp.inspection.UnexpectedAnchorInspection"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,11 @@
<html>
<body>
Reports <code>^</code> or <code>\A</code> anchors not at the beginning of the pattern and
<code>$</code>, <code>\Z</code> or <code>\z</code> anchors not at the end of the pattern.
In the wrong position these RegExp anchors prevent the pattern from matching anything.
In case of the <code>^</code> and <code>$</code> anchors, most likely the literal character was meant and the escape forgotten.
<!-- tooltip end -->
<p>
<small>New in 2018.1</small>
</body>
</html>
@@ -0,0 +1,101 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package org.intellij.lang.regexp.inspection;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.util.PsiTreeUtil;
import org.intellij.lang.regexp.psi.*;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
/**
* @author Bas Leijdekkers
*/
public class UnexpectedAnchorInspection extends LocalInspectionTool {
@Nls
@NotNull
@Override
public String getDisplayName() {
return "Begin or end anchor in unexpected position";
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new UnexpectedAnchorVisitor(holder);
}
private static class UnexpectedAnchorVisitor extends RegExpElementVisitor {
private final ProblemsHolder myHolder;
public UnexpectedAnchorVisitor(ProblemsHolder holder) {
myHolder = holder;
}
@Override
public void visitRegExpBoundary(RegExpBoundary boundary) {
super.visitRegExpBoundary(boundary);
final RegExpBoundary.Type type = boundary.getType();
switch (type) {
case BEGIN: // \A
if (!hasUnexpectedSibling(boundary, false, false)) return;
break;
case LINE_START: // ^
if (!hasUnexpectedSibling(boundary, false, true)) return;
break;
case END: // \z
case END_NO_LINE_TERM: // \Z
if (!hasUnexpectedSibling(boundary, true, false)) return;
break;
case LINE_END: // $
if (!hasUnexpectedSibling(boundary, true, true)) return;
break;
default:
return;
}
myHolder.registerProblem(boundary, "Anchor <code>#ref</code> in unexpected position");
}
private static boolean hasUnexpectedSibling(PsiElement element, boolean next, boolean line) {
PsiElement sibling = next
? PsiTreeUtil.skipSiblingsForward(element, PsiComment.class, PsiWhiteSpace.class, RegExpSetOptions.class)
: PsiTreeUtil.skipSiblingsBackward(element, PsiComment.class, PsiWhiteSpace.class, RegExpSetOptions.class);
if (sibling == null) {
return false;
}
if (line) {
if (sibling instanceof RegExpChar) {
final int value = ((RegExpChar)sibling).getValue();
return value != '\n' && value != '\r';
}
else if (sibling instanceof RegExpSimpleClass) {
final RegExpSimpleClass.Kind kind = ((RegExpSimpleClass)sibling).getKind();
switch (kind) {
case ANY:
case NON_DIGIT:
case NON_WORD:
case SPACE:
case NON_HORIZONTAL_SPACE:
case NON_VERTICAL_SPACE:
case NON_XML_NAME_START:
case NON_XML_NAME_PART:
case UNICODE_LINEBREAK:
return false;
default:
return true;
}
}
return sibling instanceof RegExpBoundary;
}
return true;
}
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package org.intellij.lang.regexp.inspection;
import com.intellij.codeInspection.LocalInspectionTool;
import org.jetbrains.annotations.NotNull;
/**
* @author Bas Leijdekkers
*/
@SuppressWarnings("RegExpUnexpectedAnchor")
public class UnexpectedAnchorInspectionTest extends RegExpInspectionTestCase {
public void testSimple() {
highlightTest("<warning descr=\"Anchor '$' in unexpected position\">$</warning><warning descr=\"Anchor '^' in unexpected position\">^</warning>");
}
public void testAZ() {
highlightTest("\n<warning descr=\"Anchor '\\A' in unexpected position\">\\A</warning><warning descr=\"Anchor '\\Z' in unexpected position\">\\Z</warning>\n");
}
public void testNoWarn() {
highlightTest("^$");
highlightTest("\n^$\n");
}
public void testCommentMode() {
highlightTest("(?x)\n" +
"# comment\n" +
"^impedance");
}
public void testIDEA184428() {
highlightTest("\\(\\s*<=\\s*;\\s*`<warning descr=\"Anchor '$' in unexpected position\">$</warning>\"SeqNo\"\\s*;\\s*-?\\d+j\\s*\\)s");
}
@NotNull
@Override
protected LocalInspectionTool getInspection() {
return new UnexpectedAnchorInspection();
}
}