| /* |
| * 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.documentation; |
| |
| import com.intellij.codeInsight.TargetElementUtilBase; |
| import com.intellij.ide.actions.ShowSettingsUtilImpl; |
| import com.intellij.lang.documentation.AbstractDocumentationProvider; |
| import com.intellij.lang.documentation.ExternalDocumentationProvider; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.ModalityState; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.projectRoots.Sdk; |
| import com.intellij.openapi.roots.ProjectRootManager; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.*; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.QualifiedName; |
| import com.intellij.util.Function; |
| import com.jetbrains.python.PyNames; |
| import com.jetbrains.python.codeInsight.PyCodeInsightSettings; |
| import com.jetbrains.python.console.PydevConsoleRunner; |
| import com.jetbrains.python.console.PydevDocumentationProvider; |
| import com.jetbrains.python.debugger.PySignature; |
| import com.jetbrains.python.debugger.PySignatureCacheManager; |
| import com.jetbrains.python.debugger.PySignatureUtil; |
| import com.jetbrains.python.psi.*; |
| import com.jetbrains.python.psi.impl.PyBuiltinCache; |
| import com.jetbrains.python.psi.resolve.QualifiedNameFinder; |
| import com.jetbrains.python.psi.types.PyClassType; |
| import com.jetbrains.python.psi.types.PyType; |
| import com.jetbrains.python.psi.types.PyTypeParser; |
| import com.jetbrains.python.psi.types.TypeEvalContext; |
| import com.jetbrains.python.toolbox.ChainIterable; |
| import com.jetbrains.python.toolbox.FP; |
| import org.apache.commons.httpclient.HttpClient; |
| import org.apache.commons.httpclient.methods.HeadMethod; |
| import org.apache.commons.httpclient.params.HttpConnectionManagerParams; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import static com.jetbrains.python.documentation.DocumentationBuilderKit.*; |
| |
| /** |
| * Provides quick docs for classes, methods, and functions. |
| * Generates documentation stub |
| */ |
| public class PythonDocumentationProvider extends AbstractDocumentationProvider implements ExternalDocumentationProvider { |
| |
| @NonNls static final String LINK_TYPE_CLASS = "#class#"; |
| @NonNls static final String LINK_TYPE_PARENT = "#parent#"; |
| @NonNls static final String LINK_TYPE_PARAM = "#param#"; |
| @NonNls static final String LINK_TYPE_TYPENAME = "#typename#"; |
| |
| @NonNls private static final String RST_PREFIX = ":"; |
| @NonNls private static final String EPYDOC_PREFIX = "@"; |
| |
| // provides ctrl+hover info |
| @Override |
| @Nullable |
| public String getQuickNavigateInfo(final PsiElement element, final PsiElement originalElement) { |
| for (final PythonDocumentationQuickInfoProvider point : PythonDocumentationQuickInfoProvider.EP_NAME.getExtensions()) { |
| String info = point.getQuickInfo(originalElement); |
| if (info != null) { |
| return info; |
| } |
| } |
| |
| if (element instanceof PyFunction) { |
| PyFunction func = (PyFunction)element; |
| StringBuilder cat = new StringBuilder(); |
| PyClass cls = func.getContainingClass(); |
| if (cls != null) { |
| String cls_name = cls.getName(); |
| cat.append("class ").append(cls_name).append("\n"); |
| // It would be nice to have class import info here, but we don't know the ctrl+hovered reference and context |
| } |
| String summary = ""; |
| final PyStringLiteralExpression docStringExpression = func.getDocStringExpression(); |
| if (docStringExpression != null) { |
| final StructuredDocString docString = DocStringUtil.parse(docStringExpression.getStringValue()); |
| if (docString != null) { |
| summary = docString.getSummary(); |
| } |
| } |
| return $(cat.toString()).add(describeDecorators(func, LSame2, ", ", LSame1)).add(describeFunction(func, LSame2, LSame1)) |
| .toString() + "\n" + summary; |
| } |
| else if (element instanceof PyClass) { |
| PyClass cls = (PyClass)element; |
| String summary = ""; |
| PyStringLiteralExpression docStringExpression = cls.getDocStringExpression(); |
| if (docStringExpression == null) { |
| final PyFunction initOrNew = cls.findInitOrNew(false); |
| if (initOrNew != null) { |
| docStringExpression = initOrNew.getDocStringExpression(); |
| } |
| } |
| if (docStringExpression != null) { |
| final StructuredDocString docString = DocStringUtil.parse(docStringExpression.getStringValue()); |
| if (docString != null) { |
| summary = docString.getSummary(); |
| } |
| } |
| |
| return describeDecorators(cls, LSame2, ", ", LSame1).add(describeClass(cls, LSame2, false, false)).toString() + "\n" + summary; |
| } |
| else if (element instanceof PyExpression) { |
| return describeExpression((PyExpression)element, originalElement); |
| } |
| return null; |
| } |
| |
| /** |
| * Creates a HTML description of function definition. |
| * |
| * @param fun the function |
| * @param func_name_wrapper puts a tag around the function name |
| * @param escaper sanitizes values that come directly from doc string or code |
| * @return chain of strings for further chaining |
| */ |
| static ChainIterable<String> describeFunction( |
| PyFunction fun, |
| FP.Lambda1<Iterable<String>, Iterable<String>> func_name_wrapper, |
| FP.Lambda1<String, String> escaper |
| ) { |
| ChainIterable<String> cat = new ChainIterable<String>(); |
| final String name = fun.getName(); |
| cat.addItem("def ").addWith(func_name_wrapper, $(name)); |
| final TypeEvalContext context = TypeEvalContext.userInitiated(fun.getContainingFile()); |
| final List<PyParameter> parameters = PyUtil.getParameters(fun, context); |
| final String paramStr = "(" + |
| StringUtil.join(parameters, |
| new Function<PyParameter, String>() { |
| @Override |
| public String fun(PyParameter parameter) { |
| return PyUtil.getReadableRepr(parameter, false); |
| } |
| }, |
| ", ") + |
| ")"; |
| cat.addItem(escaper.apply(paramStr)); |
| if (!PyNames.INIT.equals(name)) { |
| cat.addItem(escaper.apply("\nInferred type: ")); |
| getTypeDescription(fun, cat); |
| cat.addItem(BR); |
| } |
| return cat; |
| } |
| |
| @Nullable |
| private static String describeExpression(@NotNull PyExpression expr, @NotNull PsiElement originalElement) { |
| final String name = expr.getName(); |
| if (name != null) { |
| StringBuilder result = new StringBuilder((expr instanceof PyNamedParameter) ? "parameter" : "variable"); |
| result.append(String.format(" \"%s\"", name)); |
| if (expr instanceof PyNamedParameter) { |
| final PyFunction function = PsiTreeUtil.getParentOfType(expr, PyFunction.class); |
| if (function != null) { |
| result.append(" of ").append(function.getContainingClass() == null ? "function" : "method"); |
| result.append(String.format(" \"%s\"", function.getName())); |
| } |
| } |
| if (originalElement instanceof PyTypedElement) { |
| result.append("\n").append(describeType((PyTypedElement)originalElement)); |
| } |
| return result.toString(); |
| } |
| return null; |
| } |
| |
| static String describeType(@NotNull PyTypedElement element) { |
| final TypeEvalContext context = TypeEvalContext.userInitiated(element.getContainingFile()); |
| return String.format("Inferred type: %s", getTypeName(context.getType(element), context)); |
| } |
| |
| public static void getTypeDescription(@NotNull PyFunction fun, ChainIterable<String> body) { |
| final TypeEvalContext context = TypeEvalContext.userInitiated(fun.getContainingFile()); |
| PyTypeModelBuilder builder = new PyTypeModelBuilder(context); |
| builder.build(context.getType(fun), true).toBodyWithLinks(body, fun); |
| } |
| |
| public static String getTypeName(@Nullable PyType type, @NotNull final TypeEvalContext context) { |
| PyTypeModelBuilder.TypeModel typeModel = buildTypeModel(type, context); |
| return typeModel.asString(); |
| } |
| |
| private static PyTypeModelBuilder.TypeModel buildTypeModel(PyType type, TypeEvalContext context) { |
| PyTypeModelBuilder builder = new PyTypeModelBuilder(context); |
| return builder.build(type, true); |
| } |
| |
| public static void describeExpressionTypeWithLinks(ChainIterable<String> body, |
| PyReferenceExpression expression, |
| @NotNull TypeEvalContext context) { |
| PyType type = context.getType(expression); |
| describeTypeWithLinks(body, expression, type, context); |
| } |
| |
| public static void describeTypeWithLinks(ChainIterable<String> body, |
| PsiElement anchor, |
| PyType type, TypeEvalContext context) { |
| PyTypeModelBuilder builder = new PyTypeModelBuilder(context); |
| builder.build(type, true).toBodyWithLinks(body, anchor); |
| } |
| |
| |
| static ChainIterable<String> describeDecorators(PyDecoratable what, FP.Lambda1<Iterable<String>, Iterable<String>> deco_name_wrapper, |
| String deco_separator, FP.Lambda1<String, String> escaper) { |
| ChainIterable<String> cat = new ChainIterable<String>(); |
| PyDecoratorList deco_list = what.getDecoratorList(); |
| if (deco_list != null) { |
| for (PyDecorator deco : deco_list.getDecorators()) { |
| cat.add(describeDeco(deco, deco_name_wrapper, escaper)).addItem(deco_separator); // can't easily pass describeDeco to map() %) |
| } |
| } |
| return cat; |
| } |
| |
| /** |
| * Creates a HTML description of function definition. |
| * |
| * @param cls the class |
| * @param name_wrapper wrapper to render the name with |
| * @param allow_html |
| * @param link_own_name if true, add link to class's own name @return cat for easy chaining |
| */ |
| static ChainIterable<String> describeClass(PyClass cls, |
| FP.Lambda1<Iterable<String>, Iterable<String>> name_wrapper, |
| boolean allow_html, |
| boolean link_own_name) { |
| ChainIterable<String> cat = new ChainIterable<String>(); |
| final String name = cls.getName(); |
| cat.addItem("class "); |
| if (allow_html && link_own_name) { |
| cat.addWith(LinkMyClass, $(name)); |
| } |
| else { |
| cat.addWith(name_wrapper, $(name)); |
| } |
| final PyExpression[] ancestors = cls.getSuperClassExpressions(); |
| if (ancestors.length > 0) { |
| cat.addItem("("); |
| boolean is_not_first = false; |
| for (PyExpression parent : ancestors) { |
| final String parentName = parent.getName(); |
| if (parentName == null) { |
| continue; |
| } |
| if (is_not_first) { |
| cat.addItem(", "); |
| } |
| else { |
| is_not_first = true; |
| } |
| if (allow_html) { |
| cat.addWith(new LinkWrapper(LINK_TYPE_PARENT + parentName), $(parentName)); |
| } |
| else { |
| cat.addItem(parentName); |
| } |
| } |
| cat.addItem(")"); |
| } |
| return cat; |
| } |
| |
| // |
| private static Iterable<String> describeDeco(PyDecorator deco, |
| final FP.Lambda1<Iterable<String>, Iterable<String>> name_wrapper, |
| // addWith in tags, if need be |
| final FP.Lambda1<String, String> arg_wrapper |
| // add escaping, if need be |
| ) { |
| ChainIterable<String> cat = new ChainIterable<String>(); |
| cat.addItem("@").addWith(name_wrapper, $(PyUtil.getReadableRepr(deco.getCallee(), true))); |
| if (deco.hasArgumentList()) { |
| PyArgumentList arglist = deco.getArgumentList(); |
| if (arglist != null) { |
| cat |
| .addItem("(") |
| .add(interleave(FP.map(FP.combine(LReadableRepr, arg_wrapper), arglist.getArguments()), ", ")) |
| .addItem(")") |
| ; |
| } |
| } |
| return cat; |
| } |
| |
| // provides ctrl+Q doc |
| public String generateDoc(PsiElement element, @Nullable PsiElement originalElement) { |
| if (element != null && PydevConsoleRunner.isInPydevConsole(element) || |
| originalElement != null && PydevConsoleRunner.isInPydevConsole(originalElement)) { |
| return PydevDocumentationProvider.createDoc(element, originalElement); |
| } |
| |
| originalElement = findRealOriginalElement(originalElement); //original element can be whitespace or bracket, |
| // but we need identifier that resolves to element |
| |
| return new PyDocumentationBuilder(element, originalElement).build(); |
| } |
| |
| private static PsiElement findRealOriginalElement(@Nullable PsiElement element) { |
| if (element == null) { |
| return null; |
| } |
| PsiFile file = element.getContainingFile(); |
| if (file == null) { |
| return element; |
| } |
| Document document = PsiDocumentManager.getInstance(element.getProject()).getDocument(file); |
| if (document == null) { |
| return element; |
| } |
| int newOffset = TargetElementUtilBase.adjustOffset(file, document, element.getTextOffset()); |
| PsiElement newElement = file.findElementAt(newOffset); |
| return newElement != null ? newElement : element; |
| } |
| |
| @Override |
| public PsiElement getDocumentationElementForLink(PsiManager psiManager, String link, PsiElement context) { |
| if (link.equals(LINK_TYPE_CLASS)) { |
| return inferContainingClassOf(context); |
| } |
| else if (link.equals(LINK_TYPE_PARAM)) { |
| return inferClassOfParameter(context); |
| } |
| else if (link.startsWith(LINK_TYPE_PARENT)) { |
| PyClass cls = inferContainingClassOf(context); |
| if (cls != null) { |
| String desired_name = link.substring(LINK_TYPE_PARENT.length()); |
| for (PyClass parent : cls.getAncestorClasses()) { |
| final String parent_name = parent.getName(); |
| if (parent_name != null && parent_name.equals(desired_name)) return parent; |
| } |
| } |
| } |
| else if (link.startsWith(LINK_TYPE_TYPENAME)) { |
| String typeName = link.substring(LINK_TYPE_TYPENAME.length()); |
| PyType type = PyTypeParser.getTypeByName(context, typeName); |
| if (type instanceof PyClassType) { |
| return ((PyClassType)type).getPyClass(); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public List<String> getUrlFor(final PsiElement element, PsiElement originalElement) { |
| final String url = getUrlFor(element, originalElement, true); |
| return url == null ? null : Collections.singletonList(url); |
| } |
| |
| @Nullable |
| private static String getUrlFor(PsiElement element, PsiElement originalElement, boolean checkExistence) { |
| PsiFileSystemItem file = element instanceof PsiFileSystemItem ? (PsiFileSystemItem)element : element.getContainingFile(); |
| if (file == null) return null; |
| if (PyNames.INIT_DOT_PY.equals(file.getName())) { |
| file = file.getParent(); |
| assert file != null; |
| } |
| Sdk sdk = PyBuiltinCache.findSdkForFile(file); |
| if (sdk == null) { |
| return null; |
| } |
| QualifiedName qName = QualifiedNameFinder.findCanonicalImportPath(element, originalElement); |
| if (qName == null) { |
| return null; |
| } |
| PythonDocumentationMap map = PythonDocumentationMap.getInstance(); |
| String pyVersion = pyVersion(sdk.getVersionString()); |
| PsiNamedElement namedElement = (element instanceof PsiNamedElement && !(element instanceof PsiFileSystemItem)) |
| ? (PsiNamedElement)element |
| : null; |
| if (namedElement instanceof PyFunction && PyNames.INIT.equals(namedElement.getName())) { |
| final PyClass containingClass = ((PyFunction)namedElement).getContainingClass(); |
| if (containingClass != null) { |
| namedElement = containingClass; |
| } |
| } |
| String url = map.urlFor(qName, namedElement, pyVersion); |
| if (url != null) { |
| if (checkExistence && !pageExists(url)) { |
| return map.rootUrlFor(qName); |
| } |
| return url; |
| } |
| for (PythonDocumentationLinkProvider provider : Extensions.getExtensions(PythonDocumentationLinkProvider.EP_NAME)) { |
| final String providerUrl = provider.getExternalDocumentationUrl(element, originalElement); |
| if (providerUrl != null) { |
| if (checkExistence && !pageExists(providerUrl)) { |
| return provider.getExternalDocumentationRoot(sdk); |
| } |
| return providerUrl; |
| } |
| } |
| return null; |
| } |
| |
| private static boolean pageExists(String url) { |
| if (new File(url).exists()) { |
| return true; |
| } |
| HttpClient client = new HttpClient(); |
| HttpConnectionManagerParams params = client.getHttpConnectionManager().getParams(); |
| params.setSoTimeout(5 * 1000); |
| params.setConnectionTimeout(5 * 1000); |
| |
| try { |
| HeadMethod method = new HeadMethod(url); |
| int rc = client.executeMethod(method); |
| if (rc == 404) { |
| return false; |
| } |
| } |
| catch (IllegalArgumentException e) { |
| return false; |
| } |
| catch (IOException ignored) { |
| } |
| return true; |
| } |
| |
| @Nullable |
| public static String pyVersion(@Nullable String versionString) { |
| String prefix = "Python "; |
| if (versionString != null && versionString.startsWith(prefix)) { |
| String version = versionString.substring(prefix.length()); |
| int dot = version.indexOf('.'); |
| if (dot > 0) { |
| dot = version.indexOf('.', dot + 1); |
| if (dot > 0) { |
| return version.substring(0, dot); |
| } |
| return version; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public String fetchExternalDocumentation(Project project, PsiElement element, List<String> docUrls) { |
| return null; |
| } |
| |
| @Override |
| public boolean hasDocumentationFor(PsiElement element, PsiElement originalElement) { |
| return getUrlFor(element, originalElement, false) != null; |
| } |
| |
| @Override |
| public boolean canPromptToConfigureDocumentation(PsiElement element) { |
| final PsiFile containingFile = element.getContainingFile(); |
| if (containingFile instanceof PyFile) { |
| final Project project = element.getProject(); |
| final VirtualFile vFile = containingFile.getVirtualFile(); |
| if (vFile != null && ProjectRootManager.getInstance(project).getFileIndex().isInLibraryClasses(vFile)) { |
| final QualifiedName qName = QualifiedNameFinder.findCanonicalImportPath(element, element); |
| if (qName != null && qName.getComponentCount() > 0) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public void promptToConfigureDocumentation(PsiElement element) { |
| final Project project = element.getProject(); |
| final QualifiedName qName = QualifiedNameFinder.findCanonicalImportPath(element, element); |
| if (qName != null && qName.getComponentCount() > 0) { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| int rc = Messages.showOkCancelDialog(project, |
| "No external documentation URL configured for module " + qName.getComponents().get(0) + |
| ".\nWould you like to configure it now?", |
| "Python External Documentation", |
| Messages.getQuestionIcon()); |
| if (rc == Messages.OK) { |
| ShowSettingsUtilImpl.showSettingsDialog(project, PythonDocumentationConfigurable.ID, ""); |
| } |
| } |
| }, ModalityState.NON_MODAL); |
| } |
| } |
| |
| @Nullable |
| private static PyClass inferContainingClassOf(PsiElement context) { |
| if (context instanceof PyClass) return (PyClass)context; |
| if (context instanceof PyFunction) { |
| return ((PyFunction)context).getContainingClass(); |
| } |
| else { |
| return PsiTreeUtil.getParentOfType(context, PyClass.class); |
| } |
| } |
| |
| @Nullable |
| private static PyClass inferClassOfParameter(PsiElement context) { |
| if (context instanceof PyNamedParameter) { |
| final PyType type = TypeEvalContext.userInitiated(context.getContainingFile()).getType((PyNamedParameter)context); |
| if (type instanceof PyClassType) { |
| return ((PyClassType)type).getPyClass(); |
| } |
| } |
| return null; |
| } |
| |
| public static final LinkWrapper LinkMyClass = new LinkWrapper(LINK_TYPE_CLASS); |
| // link item to containing class |
| |
| public static String generateDocumentationContentStub(PyFunction element, String offset, boolean checkReturn) { |
| final Module module = ModuleUtilCore.findModuleForPsiElement(element); |
| if (module == null) return ""; |
| PyDocumentationSettings documentationSettings = PyDocumentationSettings.getInstance(module); |
| String result = ""; |
| if (documentationSettings.isEpydocFormat(element.getContainingFile())) { |
| result += generateContent(element, offset, EPYDOC_PREFIX, checkReturn); |
| } |
| else if (documentationSettings.isReSTFormat(element.getContainingFile())) { |
| result += generateContent(element, offset, RST_PREFIX, checkReturn); |
| } |
| else { |
| result += offset; |
| } |
| return result; |
| } |
| |
| public static void insertDocStub(PyFunction function, PyStatementList insertPlace, Project project, Editor editor) { |
| PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); |
| PsiWhiteSpace whitespace = PsiTreeUtil.getPrevSiblingOfType(insertPlace, PsiWhiteSpace.class); |
| String ws = "\n"; |
| if (whitespace != null) { |
| String[] spaces = whitespace.getText().split("\n"); |
| if (spaces.length > 1) { |
| ws += spaces[spaces.length - 1]; |
| } |
| } |
| String docContent = ws + generateDocumentationContentStub(function, ws, true); |
| PyExpressionStatement string = elementGenerator.createDocstring("\"\"\"" + docContent + "\"\"\""); |
| if (insertPlace != null) { |
| final PyStatement[] statements = insertPlace.getStatements(); |
| if (statements.length != 0) { |
| insertPlace.addBefore(string, statements[0]); |
| } |
| } |
| PyStringLiteralExpression docstring = function.getDocStringExpression(); |
| if (editor != null && docstring != null) { |
| int offset = docstring.getTextOffset(); |
| editor.getCaretModel().moveToOffset(offset); |
| editor.getCaretModel().moveCaretRelatively(0, 1, false, false, false); |
| } |
| } |
| |
| public String generateDocumentationContentStub(PyFunction element, boolean checkReturn) { |
| PsiWhiteSpace whitespace = PsiTreeUtil.getPrevSiblingOfType(element.getStatementList(), PsiWhiteSpace.class); |
| String ws = "\n"; |
| if (whitespace != null) { |
| String[] spaces = whitespace.getText().split("\n"); |
| if (spaces.length > 1) { |
| ws += whitespace.getText().split("\n")[1]; |
| } |
| } |
| return generateDocumentationContentStub(element, ws, checkReturn); |
| } |
| |
| private static String generateContent(PyFunction function, String offset, String prefix, boolean checkReturn) { |
| //TODO: this code duplicates PyDocstringGenerator in some parts |
| |
| final StringBuilder builder = new StringBuilder(offset); |
| final TypeEvalContext context = TypeEvalContext.userInitiated(function.getContainingFile()); |
| PySignature signature = PySignatureCacheManager.getInstance(function.getProject()).findSignature(function); |
| final PyDecoratorList decoratorList = function.getDecoratorList(); |
| final PyDecorator classMethod = decoratorList == null ? null : decoratorList.findDecorator(PyNames.CLASSMETHOD); |
| for (PyParameter p : PyUtil.getParameters(function, context)) { |
| final String parameterName = p.getName(); |
| if (p.getText().equals(PyNames.CANONICAL_SELF) || parameterName == null) { |
| continue; |
| } |
| if (classMethod != null && parameterName.equals(PyNames.CANONICAL_CLS)) continue; |
| String argType = signature == null ? null : signature.getArgTypeQualifiedName(parameterName); |
| |
| if (argType == null) { |
| builder.append(prefix); |
| builder.append("param "); |
| builder.append(parameterName); |
| builder.append(": "); |
| builder.append(offset); |
| } |
| if (PyCodeInsightSettings.getInstance().INSERT_TYPE_DOCSTUB || argType != null) { |
| builder.append(prefix); |
| builder.append("type "); |
| builder.append(parameterName); |
| builder.append(": "); |
| if (signature != null && argType != null) { |
| builder.append(PySignatureUtil.getShortestImportableName(function, argType)); |
| } |
| builder.append(offset); |
| } |
| } |
| builder.append(generateRaiseOrReturn(function, offset, prefix, checkReturn)); |
| return builder.toString(); |
| } |
| |
| public static String generateRaiseOrReturn(PyFunction element, String offset, String prefix, boolean checkReturn) { |
| StringBuilder builder = new StringBuilder(); |
| if (checkReturn) { |
| RaiseVisitor visitor = new RaiseVisitor(); |
| PyStatementList statementList = element.getStatementList(); |
| statementList.accept(visitor); |
| if (visitor.myHasReturn) { |
| builder.append(prefix).append("return:").append(offset); |
| if (PyCodeInsightSettings.getInstance().INSERT_TYPE_DOCSTUB) { |
| builder.append(prefix).append("rtype:").append(offset); |
| } |
| } |
| if (visitor.myHasRaise) { |
| builder.append(prefix).append("raise"); |
| if (visitor.myRaiseTarget != null) { |
| String raiseTarget = visitor.myRaiseTarget.getText(); |
| if (visitor.myRaiseTarget instanceof PyCallExpression) { |
| final PyExpression callee = ((PyCallExpression)visitor.myRaiseTarget).getCallee(); |
| if (callee != null) { |
| raiseTarget = callee.getText(); |
| } |
| } |
| builder.append(" ").append(raiseTarget); |
| } |
| builder.append(":").append(offset); |
| } |
| } |
| else { |
| builder.append(prefix).append("return:").append(offset); |
| if (PyCodeInsightSettings.getInstance().INSERT_TYPE_DOCSTUB) { |
| builder.append(prefix).append("rtype:").append(offset); |
| } |
| } |
| return builder.toString(); |
| } |
| |
| private static class RaiseVisitor extends PyRecursiveElementVisitor { |
| private boolean myHasRaise = false; |
| private boolean myHasReturn = false; |
| private PyExpression myRaiseTarget = null; |
| |
| @Override |
| public void visitPyRaiseStatement(PyRaiseStatement node) { |
| myHasRaise = true; |
| final PyExpression[] expressions = node.getExpressions(); |
| if (expressions.length > 0) myRaiseTarget = expressions[0]; |
| } |
| |
| @Override |
| public void visitPyReturnStatement(PyReturnStatement node) { |
| myHasReturn = true; |
| } |
| } |
| } |