Merge remote-tracking branch 'origin/master'

Conflicts:
	python/community/psi-api/src/com/jetbrains/python/PyNames.java
This commit is contained in:
Ekaterina Tuzova
2013-10-15 10:16:11 +02:00
12 changed files with 213 additions and 89 deletions
@@ -119,6 +119,8 @@ public class PyNames {
public static final String COLLECTIONS = "collections";
public static final String COLLECTIONS_NAMEDTUPLE = COLLECTIONS + "." + NAMEDTUPLE;
public static final String FORMAT = "format";
public static final String ABSTRACTMETHOD = "abc.abstractmethod";
public static final String ABSTRACTPROPERTY = "abc.abstractproperty";
+1 -1
View File
@@ -3,7 +3,7 @@
<id>org.jetbrains.plugins.rest</id>
<description>This plugin enables support for reStructuredText files (*.rst)</description>
<vendor>JetBrains</vendor>
<version>130.SNAPSHOT</version>
<version>132.SNAPSHOT</version>
<idea-version since-build="130.1" until-build="133.0"/>
<depends>com.intellij.modules.lang</depends>
@@ -0,0 +1,138 @@
package com.jetbrains.python.codeInsight;
import com.intellij.lang.injection.MultiHostRegistrar;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.jetbrains.python.inspections.PyStringFormatParser.*;
/**
* @author vlan
*/
public class PyInjectionUtil {
private PyInjectionUtil() {}
/**
* Returns true if the element is the largest expression that represents a string literal, possibly with concatenation, parentheses,
* or formatting.
*/
public static boolean isLargestStringLiteral(@NotNull PsiElement element) {
final PsiElement parent = element.getParent();
return isStringLiteralPart(element) && (parent == null || !isStringLiteralPart(parent));
}
/**
* Registers language injections in the given registrar for the specified string literal element or its ancestor that contains
* string concatenations or formatting.
*/
public static void registerStringLiteralInjection(@NotNull PsiElement element, @NotNull MultiHostRegistrar registrar) {
processStringLiteral(element, registrar, "", "", Formatting.NONE);
}
private static boolean isStringLiteralPart(@NotNull PsiElement element) {
if (element instanceof PyStringLiteralExpression) {
return true;
}
else if (element instanceof PyParenthesizedExpression) {
final PyExpression contained = ((PyParenthesizedExpression)element).getContainedExpression();
return contained != null && isStringLiteralPart(contained);
}
else if (element instanceof PyBinaryExpression) {
final PyBinaryExpression expr = (PyBinaryExpression)element;
final PyExpression left = expr.getLeftExpression();
final PyExpression right = expr.getRightExpression();
return (expr.isOperator("+") && (isStringLiteralPart(left) || right != null && isStringLiteralPart(right))) ||
expr.isOperator("%") && isStringLiteralPart(left);
}
else if (element instanceof PyCallExpression) {
final PyExpression qualifier = getFormatCallQualifier((PyCallExpression)element);
return qualifier != null && isStringLiteralPart(qualifier);
}
return false;
}
@Nullable
private static PyExpression getFormatCallQualifier(@NotNull PyCallExpression element) {
final PyExpression callee = element.getCallee();
if (callee instanceof PyQualifiedExpression) {
final PyQualifiedExpression qualifiedExpr = (PyQualifiedExpression)callee;
final PyExpression qualifier = qualifiedExpr.getQualifier();
if (qualifier != null && PyNames.FORMAT.equals(qualifiedExpr.getReferencedName())) {
return qualifier;
}
}
return null;
}
private static void processStringLiteral(@NotNull PsiElement element, @NotNull MultiHostRegistrar registrar, @NotNull String prefix,
@NotNull String suffix, @NotNull Formatting formatting) {
final String missingValue = "missing";
if (element instanceof PyStringLiteralExpression) {
final PyStringLiteralExpression expr = (PyStringLiteralExpression)element;
final List<TextRange> ranges = expr.getStringValueTextRanges();
final String text = expr.getText();
for (TextRange range : ranges) {
if (formatting != Formatting.NONE) {
final String part = range.substring(text);
final List<FormatStringChunk> chunks = formatting == Formatting.NEW_STYLE ? parseNewStyleFormat(part) : parsePercentFormat(part);
for (int i = 0; i < chunks.size(); i++) {
final FormatStringChunk chunk = chunks.get(i);
if (chunk instanceof ConstantChunk) {
final int nextIndex = i + 1;
final String chunkPrefix = i == 1 && chunks.get(0) instanceof SubstitutionChunk ? missingValue : "";
final String chunkSuffix = nextIndex < chunks.size() &&
chunks.get(nextIndex) instanceof SubstitutionChunk ? missingValue : "";
final TextRange chunkRange = chunk.getTextRange().shiftRight(range.getStartOffset());
registrar.addPlace(chunkPrefix, chunkSuffix, expr, chunkRange);
}
}
}
else {
registrar.addPlace(prefix, suffix, expr, range);
}
}
}
else if (element instanceof PyParenthesizedExpression) {
final PyExpression contained = ((PyParenthesizedExpression)element).getContainedExpression();
if (contained != null) {
processStringLiteral(contained, registrar, prefix, suffix, formatting);
}
}
else if (element instanceof PyBinaryExpression) {
final PyBinaryExpression expr = (PyBinaryExpression)element;
final PyExpression left = expr.getLeftExpression();
final PyExpression right = expr.getRightExpression();
final boolean isLeftString = isStringLiteralPart(left);
if (expr.isOperator("+")) {
final boolean isRightString = right != null && isStringLiteralPart(right);
if (isLeftString) {
processStringLiteral(left, registrar, prefix, isRightString ? "" : missingValue, formatting);
}
if (isRightString) {
processStringLiteral(right, registrar, isLeftString ? "" : missingValue, suffix, formatting);
}
}
else if (expr.isOperator("%")) {
processStringLiteral(left, registrar, prefix, suffix, Formatting.PERCENT);
}
}
else if (element instanceof PyCallExpression) {
final PyExpression qualifier = getFormatCallQualifier((PyCallExpression)element);
if (qualifier != null) {
processStringLiteral(qualifier, registrar, prefix, suffix, Formatting.NEW_STYLE);
}
}
}
private enum Formatting {
NONE,
PERCENT,
NEW_STYLE
}
}
@@ -3,11 +3,11 @@ package com.jetbrains.python.codeInsight.regexp;
import com.intellij.lang.Language;
import com.intellij.lang.injection.MultiHostInjector;
import com.intellij.lang.injection.MultiHostRegistrar;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.codeInsight.PyInjectionUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import org.jetbrains.annotations.NotNull;
@@ -51,7 +51,7 @@ public class PythonRegexpInjector implements MultiHostInjector {
@Override
public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) {
final PsiElement contextParent = context.getParent();
if (isStringLiteral(context) && contextParent instanceof PyArgumentList) {
if (PyInjectionUtil.isLargestStringLiteral(context) && contextParent instanceof PyArgumentList) {
final PyExpression[] args = ((PyArgumentList)contextParent).getArguments();
int index = ArrayUtil.indexOf(args, context);
PyCallExpression call = PsiTreeUtil.getParentOfType(context, PyCallExpression.class);
@@ -64,74 +64,20 @@ public class PythonRegexpInjector implements MultiHostInjector {
if (element != null && element.getContainingFile().getName().equals("re.py") && isRegexpMethod(element, index)) {
final Language language = isVerbose(call) ? PythonVerboseRegexpLanguage.INSTANCE : PythonRegexpLanguage.INSTANCE;
registrar.startInjecting(language);
processStringLiteral(context, registrar);
PyInjectionUtil.registerStringLiteralInjection(context, registrar);
registrar.doneInjecting();
}
}
}
}
}
}
private static boolean isStringLiteral(@NotNull PsiElement element) {
if (element instanceof PyStringLiteralExpression) {
return true;
}
else if (element instanceof PyParenthesizedExpression) {
final PyExpression contained = ((PyParenthesizedExpression)element).getContainedExpression();
return contained != null && isStringLiteral(contained);
}
else if (element instanceof PyBinaryExpression) {
final PyBinaryExpression expr = (PyBinaryExpression)element;
final PyExpression left = expr.getLeftExpression();
final PyExpression right = expr.getRightExpression();
return expr.isOperator("+") && (isStringLiteral(left) || right != null && isStringLiteral(right));
}
return false;
}
private static void processStringLiteral(@NotNull PsiElement element, @NotNull MultiHostRegistrar registrar) {
processStringLiteral(element, registrar, "", "");
}
private static void processStringLiteral(@NotNull PsiElement element, @NotNull MultiHostRegistrar registrar, @NotNull String prefix,
@NotNull String suffix) {
final String missingValue = "missing";
if (element instanceof PyStringLiteralExpression) {
final PyStringLiteralExpression expr = (PyStringLiteralExpression)element;
final List<TextRange> ranges = expr.getStringValueTextRanges();
for (TextRange range : ranges) {
registrar.addPlace(prefix, suffix, expr, range);
}
}
else if (element instanceof PyParenthesizedExpression) {
final PyExpression contained = ((PyParenthesizedExpression)element).getContainedExpression();
if (contained != null) {
processStringLiteral(contained, registrar, prefix, suffix);
}
}
else if (element instanceof PyBinaryExpression) {
final PyBinaryExpression expr = (PyBinaryExpression)element;
if (expr.isOperator("+")) {
final PyExpression left = expr.getLeftExpression();
final PyExpression right = expr.getRightExpression();
final boolean isLeftString = isStringLiteral(left);
final boolean isRightString = right != null && isStringLiteral(right);
if (isLeftString) {
processStringLiteral(left, registrar, prefix, isRightString ? "" : missingValue);
}
if (isRightString) {
processStringLiteral(right, registrar, isLeftString ? "" : missingValue, suffix);
}
}
}
}
@NotNull
@Override
public List<? extends Class<? extends PsiElement>> elementsToInjectIn() {
return Arrays.asList(PyStringLiteralExpression.class, PyParenthesizedExpression.class, PyBinaryExpression.class);
return Arrays.asList(PyStringLiteralExpression.class, PyParenthesizedExpression.class, PyBinaryExpression.class,
PyCallExpression.class);
}
private static boolean isVerbose(@NotNull PyCallExpression call) {
@@ -23,6 +23,9 @@ import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import static com.jetbrains.python.inspections.PyStringFormatParser.filterSubstitutions;
import static com.jetbrains.python.inspections.PyStringFormatParser.parsePercentFormat;
/**
* @author Alexey.Ivanov
*/
@@ -332,8 +335,8 @@ public class PyStringFormatInspection extends PyInspection {
}
private void inspectFormat(@NotNull final PyStringLiteralExpression formatExpression) {
PyStringFormatParser parser = new PyStringFormatParser(formatExpression.getStringValue());
final List<PyStringFormatParser.SubstitutionChunk> chunks = parser.parseSubstitutions();
final String value = formatExpression.getStringValue();
final List<PyStringFormatParser.SubstitutionChunk> chunks = filterSubstitutions(parsePercentFormat(value));
// 1. The '%' character
// Skip the first item in the sections, it's always empty
@@ -4,6 +4,7 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.HashMap;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl;
import org.jetbrains.annotations.NotNull;
@@ -145,10 +146,15 @@ public class PyStringFormatParser {
private static final String LENGTH_MODIFIERS = "hlL";
private static final String VALID_CONVERSION_TYPES = "diouxXeEfFgGcrs";
public PyStringFormatParser(@NotNull String literal) {
private PyStringFormatParser(@NotNull String literal) {
myLiteral = literal;
}
@NotNull
public static List<FormatStringChunk> parsePercentFormat(@NotNull String s) {
return new PyStringFormatParser(s).parse();
}
@NotNull
public static List<FormatStringChunk> parseNewStyleFormat(@NotNull String s) {
final List<FormatStringChunk> results = new ArrayList<FormatStringChunk>();
@@ -191,13 +197,13 @@ public class PyStringFormatParser {
pos = next + 1;
}
if (pos < n) {
results.add(new ConstantChunk(pos, n + 1));
results.add(new ConstantChunk(pos, n));
}
return results;
}
@NotNull
public List<FormatStringChunk> parse() {
private List<FormatStringChunk> parse() {
myPos = 0;
while(myPos < myLiteral.length()) {
int next = myLiteral.indexOf('%', myPos);
@@ -277,11 +283,6 @@ public class PyStringFormatParser {
return myLiteral.substring(flagStart, myPos);
}
@NotNull
public List<SubstitutionChunk> parseSubstitutions() {
return filterSubstitutions(parse());
}
@NotNull
public static List<SubstitutionChunk> filterSubstitutions(@NotNull List<FormatStringChunk> chunks) {
final List<SubstitutionChunk> results = new ArrayList<SubstitutionChunk>();
@@ -353,7 +354,7 @@ public class PyStringFormatParser {
if (parent instanceof PyQualifiedExpression) {
final PyQualifiedExpression qualifiedExpr = (PyQualifiedExpression)parent;
final String name = qualifiedExpr.getReferencedName();
if ("format".equals(name)) {
if (PyNames.FORMAT.equals(name)) {
final PsiElement parent2 = qualifiedExpr.getParent();
if (parent2 instanceof PyCallExpression) {
final PyCallExpression callExpr = (PyCallExpression)parent2;
@@ -24,6 +24,9 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.jetbrains.python.PyTokenTypes.*;
import static com.jetbrains.python.inspections.PyStringFormatParser.filterSubstitutions;
import static com.jetbrains.python.inspections.PyStringFormatParser.parseNewStyleFormat;
import static com.jetbrains.python.inspections.PyStringFormatParser.parsePercentFormat;
/**
* @author Dennis.Ushakov
@@ -94,10 +97,10 @@ public class PyReplaceExpressionUtil implements PyElementTypes {
final List<PyStringFormatParser.SubstitutionChunk> substitutions;
if (newStyleFormatValue != null) {
substitutions = PyStringFormatParser.filterSubstitutions(PyStringFormatParser.parseNewStyleFormat(fullText));
substitutions = filterSubstitutions(parseNewStyleFormat(fullText));
}
else {
substitutions = new PyStringFormatParser(fullText).parseSubstitutions();
substitutions = filterSubstitutions(parsePercentFormat(fullText));
}
final boolean hasSubstitutions = substitutions.size() > 0;
@@ -31,7 +31,6 @@ import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonStringUtil;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.inspections.PyStringFormatParser;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.types.PyNoneType;
@@ -306,7 +305,7 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
}
private boolean breaksStringFormatting(@NotNull String s, @NotNull TextRange range) {
return breaksRanges(substitutionsToRanges(new PyStringFormatParser(s).parseSubstitutions()), range);
return breaksRanges(substitutionsToRanges(filterSubstitutions(parsePercentFormat(s))), range);
}
private boolean breaksNewStyleStringFormatting(@NotNull String s, @NotNull TextRange range) {
@@ -3,6 +3,7 @@ package com.jetbrains.python.sdk;
import com.google.common.collect.Lists;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
@@ -130,7 +131,9 @@ public class InterpreterPathChooser extends BaseListPopupStep<String> {
remoteInterpreterManager.addRemoteSdk(myProject, myOwnerComponent, Lists.newArrayList(myExistingSdks), myCallback);
}
else {
Messages.showErrorDialog("The Remote Hosts Access plugin is missing. Please enable the plugin in Settings | Plugins.", "Add Remote Interpreter");
Messages.showErrorDialog("The Remote Hosts Access plugin is missing. Please enable the plugin in " +
ShowSettingsUtil.getSettingsMenuName() +
" | Plugins.", "Add Remote Interpreter");
}
}
@@ -82,7 +82,7 @@ public class PythonSpellcheckerStrategy extends SpellcheckingStrategy {
@Override
public void tokenize(@NotNull PyStringLiteralExpression element, TokenConsumer consumer) {
String stringValue = element.getStringValue();
List<PyStringFormatParser.FormatStringChunk> chunks = new PyStringFormatParser(stringValue).parse();
List<PyStringFormatParser.FormatStringChunk> chunks = PyStringFormatParser.parsePercentFormat(stringValue);
Splitter splitter = PlainTextSplitter.getInstance();
for (PyStringFormatParser.FormatStringChunk chunk : chunks) {
if (chunk instanceof PyStringFormatParser.ConstantChunk) {
@@ -111,6 +111,30 @@ public class PyRegexpTest extends PyTestCase {
".*(missing)missing");
}
public void testPercentFormattingRegexpAutoInjection() {
doTestInjectedText("import re \n" +
"\n" +
"def f(x, y):\n" +
" re.search('<caret>.*%s-%d' % (x, y), 'foo')\n",
".*missing-missing");
}
public void testNewStyleFormattingRegexpAutoInjection() {
doTestInjectedText("import re\n" +
"\n" +
"def f(x, y):\n" +
" re.search('<caret>.*{foo}-{}'.format(x, foo=y), 'foo')\n",
".*missing-missing");
}
public void testNewStyleFormattingEndsWithConstant() {
doTestInjectedText("import re\n" +
"\n" +
"def f(**kwargs):" +
" re.search('<caret>(foo{bar}baz$)'.format(**kwargs), 'foo')\n",
"(foomissingbaz$)");
}
private void doTestInjectedText(@NotNull String text, @NotNull String expected) {
myFixture.configureByText(PythonFileType.INSTANCE, text);
final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());
@@ -1,7 +1,6 @@
package com.jetbrains.python;
import com.intellij.openapi.util.TextRange;
import com.jetbrains.python.inspections.PyStringFormatParser;
import junit.framework.TestCase;
import java.util.List;
@@ -13,7 +12,7 @@ import static com.jetbrains.python.inspections.PyStringFormatParser.*;
*/
public class PyStringFormatParserTest extends TestCase {
public void testSimple() {
List<PyStringFormatParser.FormatStringChunk> chunks = new PyStringFormatParser("abc").parse();
List<FormatStringChunk> chunks = parsePercentFormat("abc");
assertEquals(1, chunks.size());
assertConstant(chunks.get(0), 0, 3);
}
@@ -25,13 +24,13 @@ public class PyStringFormatParserTest extends TestCase {
}
public void testDoublePercent() {
List<PyStringFormatParser.FormatStringChunk> chunks = new PyStringFormatParser("abc%%def").parse();
List<FormatStringChunk> chunks = parsePercentFormat("abc%%def");
assertEquals(1, chunks.size());
assertConstant(chunks.get(0), 0, 8);
}
public void testFormat() {
List<PyStringFormatParser.FormatStringChunk> chunks = new PyStringFormatParser("%s").parse();
List<FormatStringChunk> chunks = parsePercentFormat("%s");
assertEquals(1, chunks.size());
SubstitutionChunk chunk = (SubstitutionChunk) chunks.get(0);
assertEquals(0, chunk.getStartIndex());
@@ -40,13 +39,13 @@ public class PyStringFormatParserTest extends TestCase {
}
public void testSubstitutionAfterFormat() {
List<PyStringFormatParser.FormatStringChunk> chunks = new PyStringFormatParser("Hello, %s").parse();
List<FormatStringChunk> chunks = parsePercentFormat("Hello, %s");
assertEquals(2, chunks.size());
assertConstant(chunks.get(0), 0, 7);
}
public void testMappingKey() {
List<PyStringFormatParser.FormatStringChunk> chunks = new PyStringFormatParser("%(language)s").parse();
List<FormatStringChunk> chunks = parsePercentFormat("%(language)s");
assertEquals(1, chunks.size());
SubstitutionChunk chunk = (SubstitutionChunk) chunks.get(0);
assertEquals("language", chunk.getMappingKey());
@@ -54,35 +53,35 @@ public class PyStringFormatParserTest extends TestCase {
}
public void testConversionFlags() {
List<PyStringFormatParser.FormatStringChunk> chunks = new PyStringFormatParser("%#0d").parse();
List<FormatStringChunk> chunks = parsePercentFormat("%#0d");
assertEquals(1, chunks.size());
SubstitutionChunk chunk = (SubstitutionChunk) chunks.get(0);
assertEquals("#0", chunk.getConversionFlags());
}
public void testWidth() {
List<PyStringFormatParser.FormatStringChunk> chunks = new PyStringFormatParser("%345d").parse();
List<FormatStringChunk> chunks = parsePercentFormat("%345d");
assertEquals(1, chunks.size());
SubstitutionChunk chunk = (SubstitutionChunk) chunks.get(0);
assertEquals("345", chunk.getWidth());
}
public void testPrecision() {
List<PyStringFormatParser.FormatStringChunk> chunks = new PyStringFormatParser("%.2d").parse();
List<FormatStringChunk> chunks = parsePercentFormat("%.2d");
assertEquals(1, chunks.size());
SubstitutionChunk chunk = (SubstitutionChunk) chunks.get(0);
assertEquals("2", chunk.getPrecision());
}
public void testLengthModifier() {
List<PyStringFormatParser.FormatStringChunk> chunks = new PyStringFormatParser("%ld").parse();
List<FormatStringChunk> chunks = parsePercentFormat("%ld");
assertEquals(1, chunks.size());
SubstitutionChunk chunk = (SubstitutionChunk) chunks.get(0);
assertEquals('l', chunk.getLengthModifier());
}
public void testDoubleAsterisk() {
List<PyStringFormatParser.FormatStringChunk> chunks = new PyStringFormatParser("%**d").parse();
List<FormatStringChunk> chunks = parsePercentFormat("%**d");
assertEquals(2, chunks.size());
SubstitutionChunk chunk = (SubstitutionChunk) chunks.get(0);
assertEquals(2, chunk.getEndIndex());
@@ -90,7 +89,7 @@ public class PyStringFormatParserTest extends TestCase {
}
public void testUnclosedMapping() {
List<PyStringFormatParser.FormatStringChunk> chunks = new PyStringFormatParser("%(name1s").parse();
List<FormatStringChunk> chunks = parsePercentFormat("%(name1s");
SubstitutionChunk chunk = (SubstitutionChunk) chunks.get(0);
assertEquals("name1s", chunk.getMappingKey());
assertTrue(chunk.isUnclosedMapping());
@@ -126,4 +125,10 @@ public class PyStringFormatParserTest extends TestCase {
assertEquals(1, chunks.size());
assertEquals(TextRange.create(1, 4), chunks.get(0).getTextRange());
}
public void testNewStyleConstant() {
List<FormatStringChunk> chunks = parseNewStyleFormat("a");
assertEquals(1, chunks.size());
assertConstant(chunks.get(0), 0, 1);
}
}