diff --git a/RegExpSupport/src/META-INF/RegExpPlugin.xml b/RegExpSupport/src/META-INF/RegExpPlugin.xml index e8910e0d82c6..4600231ea976 100644 --- a/RegExpSupport/src/META-INF/RegExpPlugin.xml +++ b/RegExpSupport/src/META-INF/RegExpPlugin.xml @@ -54,5 +54,8 @@ + diff --git a/RegExpSupport/src/inspectionDescriptions/RegExpUnexpectedAnchor.html b/RegExpSupport/src/inspectionDescriptions/RegExpUnexpectedAnchor.html new file mode 100644 index 000000000000..a9b4e86caa5b --- /dev/null +++ b/RegExpSupport/src/inspectionDescriptions/RegExpUnexpectedAnchor.html @@ -0,0 +1,11 @@ + + +Reports ^ or \A anchors not at the beginning of the pattern and +$, \Z or \z 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 ^ and $ anchors, most likely the literal character was meant and the escape forgotten. + +

+New in 2018.1 + + \ No newline at end of file diff --git a/RegExpSupport/src/org/intellij/lang/regexp/inspection/UnexpectedAnchorInspection.java b/RegExpSupport/src/org/intellij/lang/regexp/inspection/UnexpectedAnchorInspection.java new file mode 100644 index 000000000000..bd769875fdb5 --- /dev/null +++ b/RegExpSupport/src/org/intellij/lang/regexp/inspection/UnexpectedAnchorInspection.java @@ -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 #ref 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; + } + } +} diff --git a/RegExpSupport/test/org/intellij/lang/regexp/inspection/UnexpectedAnchorInspectionTest.java b/RegExpSupport/test/org/intellij/lang/regexp/inspection/UnexpectedAnchorInspectionTest.java new file mode 100644 index 000000000000..c25d878f0ea5 --- /dev/null +++ b/RegExpSupport/test/org/intellij/lang/regexp/inspection/UnexpectedAnchorInspectionTest.java @@ -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("$^"); + } + + public void testAZ() { + highlightTest("\n\\A\\Z\n"); + } + + public void testNoWarn() { + highlightTest("^$"); + highlightTest("\n^$\n"); + } + + public void testCommentMode() { + highlightTest("(?x)\n" + + "# comment\n" + + "^impedance"); + } + + public void testIDEA184428() { + highlightTest("\\(\\s*<=\\s*;\\s*`$\"SeqNo\"\\s*;\\s*-?\\d+j\\s*\\)s"); + } + + @NotNull + @Override + protected LocalInspectionTool getInspection() { + return new UnexpectedAnchorInspection(); + } +}