RegExp: the first RegExp inspection

This commit is contained in:
Bas Leijdekkers
2017-02-03 12:17:07 +01:00
parent 564d43fa48
commit 51acde88ac
4 changed files with 237 additions and 0 deletions
@@ -26,5 +26,8 @@
<className>org.intellij.lang.regexp.intention.CheckRegExpIntentionAction</className>
<category>RegExp</category>
</intentionAction>
<localInspection groupName="RegExp" language="RegExp" shortName="RepeatedSpace" displayName="Consecutive spaces" enabledByDefault="true"
level="WARNING" implementationClass="org.intellij.lang.regexp.inspection.RepeatedSpaceInspection"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,8 @@
<html>
<body>
Reports multiple consecutive spaces in a RegExp.
Because spaces are not visible by default, it can be hard to see how many spaces are required.
The RegExp can be made more clear by replacing consecutive spaces with a single space and a counted quantifier.
<!-- tooltip end -->
</body>
</html>
@@ -0,0 +1,157 @@
/*
* Copyright 2000-2017 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 org.intellij.lang.regexp.inspection;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.intellij.lang.regexp.RegExpFileType;
import org.intellij.lang.regexp.psi.*;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
/**
* @author Bas Leijdekkers
*/
public class RepeatedSpaceInspection extends LocalInspectionTool {
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new RepeatedSpaceVisitor(holder);
}
private static class RepeatedSpaceVisitor extends RegExpElementVisitor {
private final ProblemsHolder myHolder;
private int myCount = 0;
private RegExpChar myFirstChar = null;
private boolean quoted = false;
public RepeatedSpaceVisitor(ProblemsHolder holder) {
myHolder = holder;
}
@Override
public void visitRegExpChar(RegExpChar aChar) {
if (!quoted && !(aChar.getParent() instanceof RegExpClass) && aChar.getType() == RegExpChar.Type.CHAR && aChar.getValue() == ' ') {
if (myFirstChar == null) {
myFirstChar = aChar;
}
myCount++;
}
else {
super.visitRegExpChar(aChar);
}
}
@Override
public void visitWhiteSpace(PsiWhiteSpace space) {
super.visitWhiteSpace(space);
final String text = space.getText();
if (text.equals("\\Q")) {
quoted = true;
}
else if (text.equals("\\E")) {
quoted = false;
}
myFirstChar = null;
myCount = 0;
}
@Override
public void visitRegExpClass(RegExpClass expClass) {
super.visitRegExpClass(expClass);
myFirstChar = null;
myCount = 0;
}
@Override
public void visitRegExpElement(RegExpElement element) {
super.visitRegExpElement(element);
if (myFirstChar != null && myCount > 1) {
final int offset = myFirstChar.getStartOffsetInParent();
final String message = myCount + " consecutive spaces in RegExp";
myHolder.registerProblem(myFirstChar.getParent(), new TextRange(offset, offset + myCount), message,
new RepeatedSpaceFix(myCount));
}
myFirstChar = null;
myCount = 0;
}
}
private static class RepeatedSpaceFix implements LocalQuickFix {
private final int myCount;
public RepeatedSpaceFix(int count) {
myCount = count;
}
@Nls
@NotNull
@Override
public String getName() {
return "Replace with ' {" + myCount + "}'";
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return "Replace with space and repeated quantifier";
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiElement element = descriptor.getPsiElement();
if (!(element instanceof RegExpBranch)) {
return;
}
final TextRange range = descriptor.getTextRangeInElement();
final StringBuilder text = new StringBuilder();
final PsiElement[] children = element.getChildren();
boolean inserted = false;
for (PsiElement child : children) {
if (!range.contains(child.getStartOffsetInParent())) {
text.append(child.getText());
}
else if (!inserted) {
text.append(" {").append(range.getLength()).append('}');
inserted = true;
}
}
final RegExpPattern pattern = createPatternFromText(text, element);
final RegExpBranch branch = PsiTreeUtil.getChildOfType(pattern, RegExpBranch.class);
assert branch != null;
element.replace(branch);
}
@NotNull
public static RegExpPattern createPatternFromText(@NotNull final CharSequence text, @NotNull final PsiElement context) {
final PsiFileFactory factory = PsiFileFactory.getInstance(context.getProject());
final PsiFile file = factory.createFileFromText("dummy.regexp", RegExpFileType.INSTANCE, text);
final RegExpPattern pattern = PsiTreeUtil.getChildOfType(file, RegExpPattern.class);
assert pattern != null;
return pattern;
}
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2000-2017 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 org.intellij.lang.regexp.inspection;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
import org.intellij.lang.annotations.Language;
import org.intellij.lang.regexp.RegExpFileType;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import java.util.List;
/**
* @author Bas Leijdekkers
*/
public class RepeatedSpaceInspectionTest extends LightPlatformCodeInsightFixtureTestCase {
public void testSimple() {
highlightTest("<warning descr=\"2 consecutive spaces in RegExp\"> </warning>");
}
public void testIgnoreQuoted() {
highlightTest("\\Q \\E");
}
public void testIgnoreInClass() {
highlightTest("[ <warning descr=\"Duplicate character ' ' inside character class\"> </warning><warning descr=\"Duplicate character ' ' inside character class\"> </warning>]");
}
public void testReplacement() {
quickfixTest(" ", " {5}", "Replace with ' {5}'");
}
private void highlightTest(@Language("RegExp") String code) {
myFixture.enableInspections(new RepeatedSpaceInspection());
myFixture.configureByText(RegExpFileType.INSTANCE, code);
myFixture.testHighlighting();
}
private void quickfixTest(@Language("RegExp") String before, @Language("RegExp") String after, String hint) {
myFixture.enableInspections(new RepeatedSpaceInspection());
myFixture.configureByText(RegExpFileType.INSTANCE, before);
final IntentionAction intention = findIntention(hint);
assertNotNull(intention);
myFixture.launchAction(intention);
myFixture.checkResult(after);
}
public IntentionAction findIntention(@NotNull final String hint) {
final List<IntentionAction> intentions = myFixture.filterAvailableIntentions(hint);
Assert.assertFalse("\"" + hint + "\" not in " + intentions, intentions.isEmpty());
Assert.assertFalse("Too many quickfixes found for \"" + hint + "\": " + intentions + "]", intentions.size() > 1);
return intentions.get(0);
}
}