diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties
index 368475f301ba..1db11b782e1f 100644
--- a/python/src/com/jetbrains/python/PyBundle.properties
+++ b/python/src/com/jetbrains/python/PyBundle.properties
@@ -159,6 +159,9 @@ QFIX.NAME.wrap.in.exception=Wrap with Exception call
QFIX.NAME.make.list=Replace tuple with list
+#PyRemoveUnderscoresInNumericLiteralsQuickFix
+QFIX.NAME.remove.underscores.in.numeric=Remove underscores in numeric literals
+
# Intentions: INTN
INTN.Family.convert.import.unqualify=Convert 'import module' to 'from module import'
INTN.Family.convert.import.qualify=Convert 'from module import' to 'import module'
diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyRemoveUnderscoresInNumericLiteralsQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyRemoveUnderscoresInNumericLiteralsQuickFix.java
new file mode 100644
index 000000000000..87507788d251
--- /dev/null
+++ b/python/src/com/jetbrains/python/inspections/quickfix/PyRemoveUnderscoresInNumericLiteralsQuickFix.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2016 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 com.jetbrains.python.inspections.quickfix;
+
+import com.intellij.codeInspection.LocalQuickFix;
+import com.intellij.codeInspection.ProblemDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.psi.LanguageLevel;
+import com.jetbrains.python.psi.PyElementGenerator;
+import com.jetbrains.python.psi.PyNumericLiteralExpression;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NotNull;
+
+public class PyRemoveUnderscoresInNumericLiteralsQuickFix implements LocalQuickFix {
+
+ @Nls
+ @NotNull
+ @Override
+ public String getName() {
+ return PyBundle.message("QFIX.NAME.remove.underscores.in.numeric");
+ }
+
+ @Nls
+ @NotNull
+ @Override
+ public String getFamilyName() {
+ return getName();
+ }
+
+ @Override
+ public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
+ final PsiElement element = descriptor.getPsiElement();
+ if (element instanceof PyNumericLiteralExpression) {
+ final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
+ final String text = element.getText();
+
+ element.replace(elementGenerator.createExpressionFromText(LanguageLevel.forElement(element), text.replaceAll("_", "")));
+ }
+ }
+}
diff --git a/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java b/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java
index 96b5f196fdd8..3b950a09b2d6 100644
--- a/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java
+++ b/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java
@@ -239,9 +239,9 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
public void visitPyNumericLiteralExpression(final PyNumericLiteralExpression node) {
super.visitPyNumericLiteralExpression(node);
- if (node.isIntegerLiteral()) {
- final String text = node.getText();
+ final String text = node.getText();
+ if (node.isIntegerLiteral()) {
if (text.endsWith("l") || text.endsWith("L")) {
final StringBuilder message = new StringBuilder(myCommonMessage);
final String suffix = " not support a trailing \'l\' or \'L\'.";
@@ -273,6 +273,20 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
}
}
}
+
+ if (text.contains("_")) {
+ final StringBuilder message = new StringBuilder(myCommonMessage);
+ final String suffix = " not support underscores in numeric literals";
+ int len = 0;
+
+ for (LanguageLevel languageLevel : myVersionsToProcess) {
+ if (languageLevel.isOlderThan(LanguageLevel.PYTHON36)) {
+ len = appendLanguageLevel(message, len, languageLevel);
+ }
+ }
+
+ commonRegisterProblem(message, suffix, len, node, new PyRemoveUnderscoresInNumericLiteralsQuickFix());
+ }
}
@Override
diff --git a/python/testData/highlighting/underscoresInNumericLiterals.py b/python/testData/highlighting/underscoresInNumericLiterals.py
new file mode 100644
index 000000000000..829a759335c9
--- /dev/null
+++ b/python/testData/highlighting/underscoresInNumericLiterals.py
@@ -0,0 +1,58 @@
+# hex
+0xCAFE_F00D
+
+# oct
+0o1_23
+01_23
+
+# bin
+0b_0011_1111_0100_1110
+
+# dec
+10_000_000
+
+# pointfloat
+10_00.00_23
+10_00.
+
+# exponentfloat
+10_00.00_23e1_2
+10_00.00_23E1_2
+10_00.e1_2
+10_00.E1_2
+
+10_00.00_23e+1_2
+10_00.00_23E+1_2
+10_00.e+1_2
+10_00.E+1_2
+
+10_00.00_23e-1_2
+10_00.00_23E-1_2
+10_00.e-1_2
+10_00.E-1_2
+
+
+10_0000_23e1_2
+10_0000_23E1_2
+10_00e1_2
+10_00E1_2
+
+10_0000_23e+1_2
+10_0000_23E+1_2
+10_00e+1_2
+10_00E+1_2
+
+10_0000_23e-1_2
+10_0000_23E-1_2
+10_00e-1_2
+10_00E-1_2
+
+# imag
+10_00.00_23j
+10_00.00_23J
+
+10_00.00_23e1_2j
+10_00.00_23e1_2J
+
+10_000_000j
+10_000_000J
\ No newline at end of file
diff --git a/python/testData/inspections/PyCompatibilityInspection/underscoresInNumericLiterals.py b/python/testData/inspections/PyCompatibilityInspection/underscoresInNumericLiterals.py
new file mode 100644
index 000000000000..ac96de78d35e
--- /dev/null
+++ b/python/testData/inspections/PyCompatibilityInspection/underscoresInNumericLiterals.py
@@ -0,0 +1,58 @@
+# hex
+0xCAFE_F00D
+
+# oct
+0o1_23
+01_23
+
+# bin
+0b_0011_1111_0100_1110
+
+# dec
+10_000_000
+
+# pointfloat
+10_00.00_23
+10_00.
+
+# exponentfloat
+10_00.00_23e1_2
+10_00.00_23E1_2
+10_00.e1_2
+10_00.E1_2
+
+10_00.00_23e+1_2
+10_00.00_23E+1_2
+10_00.e+1_2
+10_00.E+1_2
+
+10_00.00_23e-1_2
+10_00.00_23E-1_2
+10_00.e-1_2
+10_00.E-1_2
+
+
+10_0000_23e1_2
+10_0000_23E1_2
+10_00e1_2
+10_00E1_2
+
+10_0000_23e+1_2
+10_0000_23E+1_2
+10_00e+1_2
+10_00E+1_2
+
+10_0000_23e-1_2
+10_0000_23E-1_2
+10_00e-1_2
+10_00E-1_2
+
+# imag
+10_00.00_23j
+10_00.00_23J
+
+10_00.00_23e1_2j
+10_00.00_23e1_2J
+
+10_000_000j
+10_000_000J
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java
index 917c0574b6a3..7d152a5520c7 100644
--- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java
+++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java
@@ -588,6 +588,15 @@ public class PyQuickFixTest extends PyTestCase {
true, true);
}
+ public void testRemovingUnderscoresInNumericLiterals() {
+ myFixture.configureByText(PythonFileType.INSTANCE, "1_0_0");
+
+ final IntentionAction action = myFixture.findSingleIntention(PyBundle.message("QFIX.NAME.remove.underscores.in.numeric"));
+ myFixture.launchAction(action);
+
+ myFixture.checkResult("100");
+ }
+
@Override
@NonNls
protected String getTestDataPath() {
diff --git a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java
index 5bcd1a8a2b0a..3beb8ee03954 100644
--- a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java
+++ b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java
@@ -289,6 +289,10 @@ public class PythonHighlightingTest extends PyTestCase {
doTest(LanguageLevel.PYTHON35, true, false);
}
+ public void testUnderscoresInNumericLiterals() {
+ doTest(LanguageLevel.PYTHON35, true, false);
+ }
+
// ---
private void doTest(final LanguageLevel languageLevel, final boolean checkWarnings, final boolean checkInfos) {
PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), languageLevel);
diff --git a/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java
index ca6272720622..c76c25e6d517 100644
--- a/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java
+++ b/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java
@@ -186,6 +186,10 @@ public class PyCompatibilityInspectionTest extends PyTestCase {
doTest();
}
+ public void testUnderscoresInNumericLiterals() {
+ doTest(LanguageLevel.PYTHON36);
+ }
+
private void doTest(@NotNull LanguageLevel level) {
runWithLanguageLevel(level, this::doTest);
}