diff --git a/RegExpSupport/src/META-INF/RegExpPlugin.xml b/RegExpSupport/src/META-INF/RegExpPlugin.xml
index 10cded844ef9..a169960dfb3a 100644
--- a/RegExpSupport/src/META-INF/RegExpPlugin.xml
+++ b/RegExpSupport/src/META-INF/RegExpPlugin.xml
@@ -26,5 +26,8 @@
org.intellij.lang.regexp.intention.CheckRegExpIntentionAction
RegExp
+
+
diff --git a/RegExpSupport/src/inspectionDescriptions/RepeatedSpace.html b/RegExpSupport/src/inspectionDescriptions/RepeatedSpace.html
new file mode 100644
index 000000000000..de84d704f38d
--- /dev/null
+++ b/RegExpSupport/src/inspectionDescriptions/RepeatedSpace.html
@@ -0,0 +1,8 @@
+
+
+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.
+
+
+
\ No newline at end of file
diff --git a/RegExpSupport/src/org/intellij/lang/regexp/inspection/RepeatedSpaceInspection.java b/RegExpSupport/src/org/intellij/lang/regexp/inspection/RepeatedSpaceInspection.java
new file mode 100644
index 000000000000..686d50966c47
--- /dev/null
+++ b/RegExpSupport/src/org/intellij/lang/regexp/inspection/RepeatedSpaceInspection.java
@@ -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;
+ }
+ }
+}
diff --git a/RegExpSupport/test/org/intellij/lang/regexp/inspection/RepeatedSpaceInspectionTest.java b/RegExpSupport/test/org/intellij/lang/regexp/inspection/RepeatedSpaceInspectionTest.java
new file mode 100644
index 000000000000..cb0c5015e30b
--- /dev/null
+++ b/RegExpSupport/test/org/intellij/lang/regexp/inspection/RepeatedSpaceInspectionTest.java
@@ -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(" ");
+ }
+
+ public void testIgnoreQuoted() {
+ highlightTest("\\Q \\E");
+ }
+
+ public void testIgnoreInClass() {
+ highlightTest("[ ]");
+ }
+
+ 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 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);
+ }
+}