blob: 029eabc49e803b50137603b200939894e3191188 [file] [log] [blame]
/*
* Copyright 2000-2013 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.codeInsight.regexp;
import com.intellij.lang.Language;
import com.intellij.lang.injection.MultiHostInjector;
import com.intellij.lang.injection.MultiHostRegistrar;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.psi.impl.source.tree.injected.MultiHostRegistrarImpl;
import com.intellij.psi.impl.source.tree.injected.Place;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.codeInsight.PyInjectionUtil;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author yole
*/
public class PythonRegexpInjector implements MultiHostInjector {
private static class RegexpMethodDescriptor {
@NotNull private final String methodName;
private final int argIndex;
private RegexpMethodDescriptor(@NotNull String methodName, int argIndex) {
this.methodName = methodName;
this.argIndex = argIndex;
}
}
private final List<RegexpMethodDescriptor> myDescriptors = new ArrayList<RegexpMethodDescriptor>();
public PythonRegexpInjector() {
addMethod("compile");
addMethod("search");
addMethod("match");
addMethod("split");
addMethod("findall");
addMethod("finditer");
addMethod("sub");
addMethod("subn");
}
private void addMethod(@NotNull String name) {
myDescriptors.add(new RegexpMethodDescriptor(name, 0));
}
@Override
public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) {
final PsiElement contextParent = context.getParent();
if (PyInjectionUtil.getLargestStringLiteral(context) == context && contextParent instanceof PyArgumentList) {
final PyExpression[] args = ((PyArgumentList)contextParent).getArguments();
int index = ArrayUtil.indexOf(args, context);
PyCallExpression call = PsiTreeUtil.getParentOfType(context, PyCallExpression.class);
if (call != null) {
final PyExpression callee = call.getCallee();
if (callee instanceof PyReferenceExpression && canBeRegexpCall(callee)) {
final PsiPolyVariantReference ref = ((PyReferenceExpression)callee).getReference(PyResolveContext.noImplicits());
if (ref != null) {
final PsiElement element = ref.resolve();
if (element != null && ScopeUtil.getScopeOwner(element) instanceof PyFile &&
element.getContainingFile().getName().equals("re.py") && isRegexpMethod(element, index)) {
final Language language = isVerbose(call) ? PythonVerboseRegexpLanguage.INSTANCE : PythonRegexpLanguage.INSTANCE;
registrar.startInjecting(language);
final PyInjectionUtil.InjectionResult result = PyInjectionUtil.registerStringLiteralInjection(context, registrar);
if (result.isInjected()) {
registrar.doneInjecting();
if (!result.isStrict()) {
final PsiFile file = getInjectedFile(registrar);
if (file != null) {
file.putUserData(InjectedLanguageUtil.FRANKENSTEIN_INJECTION, Boolean.TRUE);
}
}
}
}
}
}
}
}
}
@Nullable
private static PsiFile getInjectedFile(@NotNull MultiHostRegistrar registrar) {
// Don't add a dependency on IntelliLang here now, but this injector should become IntelliLang-based in the future
final List<Pair<Place,PsiFile>> result = ((MultiHostRegistrarImpl)registrar).getResult();
return result == null || result.isEmpty() ? null : result.get(result.size() - 1).second;
}
@NotNull
@Override
public List<? extends Class<? extends PsiElement>> elementsToInjectIn() {
return Arrays.asList(PyStringLiteralExpression.class, PyParenthesizedExpression.class, PyBinaryExpression.class,
PyCallExpression.class);
}
private static boolean isVerbose(@NotNull PyCallExpression call) {
PyExpression[] arguments = call.getArguments();
if (arguments.length <= 1) {
return false;
}
return isVerbose(arguments[arguments.length-1]);
}
private static boolean isVerbose(@Nullable PyExpression expr) {
if (expr instanceof PyKeywordArgument) {
PyKeywordArgument keywordArgument = (PyKeywordArgument)expr;
if (!"flags".equals(keywordArgument.getName())) {
return false;
}
return isVerbose(keywordArgument.getValueExpression());
}
if (expr instanceof PyReferenceExpression) {
return "VERBOSE".equals(((PyReferenceExpression)expr).getReferencedName());
}
if (expr instanceof PyBinaryExpression) {
return isVerbose(((PyBinaryExpression)expr).getLeftExpression()) || isVerbose(((PyBinaryExpression)expr).getRightExpression());
}
return false;
}
private boolean isRegexpMethod(@NotNull PsiElement element, int index) {
if (!(element instanceof PyFunction)) {
return false;
}
final String name = ((PyFunction)element).getName();
for (RegexpMethodDescriptor descriptor : myDescriptors) {
if (descriptor.methodName.equals(name) && descriptor.argIndex == index) {
return true;
}
}
return false;
}
private boolean canBeRegexpCall(@NotNull PyExpression callee) {
String text = callee.getText();
for (RegexpMethodDescriptor descriptor : myDescriptors) {
if (text.endsWith(descriptor.methodName)) {
return true;
}
}
return false;
}
}