private void buildFromDocstring(@NotNull final PsiElement elementDefinition, boolean isProperty) {
    PyClass pyClass = null;
    final PyStringLiteralExpression docStringExpression =
        ((PyDocStringOwner) elementDefinition).getDocStringExpression();

    if (elementDefinition instanceof PyClass) {
      pyClass = (PyClass) elementDefinition;
      myBody.add(PythonDocumentationProvider.describeDecorators(pyClass, TagItalic, BR, LCombUp));
      myBody.add(PythonDocumentationProvider.describeClass(pyClass, TagBold, true, false));
    } else if (elementDefinition instanceof PyFunction) {
      PyFunction pyFunction = (PyFunction) elementDefinition;
      if (!isProperty) {
        pyClass = pyFunction.getContainingClass();
        if (pyClass != null) {
          myBody
              .addWith(
                  TagSmall, PythonDocumentationProvider.describeClass(pyClass, TagCode, true, true))
              .addItem(BR)
              .addItem(BR);
        }
      }
      myBody
          .add(PythonDocumentationProvider.describeDecorators(pyFunction, TagItalic, BR, LCombUp))
          .add(PythonDocumentationProvider.describeFunction(pyFunction, TagBold, LCombUp));
      if (docStringExpression == null) {
        addInheritedDocString(pyFunction, pyClass);
      }
    } else if (elementDefinition instanceof PyFile) {
      addModulePath((PyFile) elementDefinition);
    }
    if (docStringExpression != null) {
      myBody.addItem(BR);
      addFormattedDocString(myElement, docStringExpression.getStringValue(), myBody, myEpilog);
    }
  }
  private void buildFromParameter(
      @NotNull final TypeEvalContext context,
      @Nullable final PsiElement outerElement,
      @NotNull final PsiElement elementDefinition) {
    myBody.addItem(combUp("Parameter " + PyUtil.getReadableRepr(elementDefinition, false)));
    boolean typeFromDocstringAdded =
        addTypeAndDescriptionFromDocstring((PyNamedParameter) elementDefinition);
    if (outerElement instanceof PyExpression) {
      PyType type = context.getType((PyExpression) outerElement);
      if (type != null) {
        String typeString = null;
        if (type instanceof PyDynamicallyEvaluatedType) {
          if (!typeFromDocstringAdded) {
            typeString = "\nDynamically inferred type: ";
          }
        } else {
          if (outerElement.getReference() != null) {
            PsiElement target = outerElement.getReference().resolve();

            if (target instanceof PyTargetExpression) {
              final String targetName = ((PyTargetExpression) target).getName();
              if (targetName != null
                  && targetName.equals(((PyNamedParameter) elementDefinition).getName())) {
                typeString = "\nReassigned value has type: ";
              }
            }
          }
        }
        if (typeString == null && !typeFromDocstringAdded) {
          typeString = "\nInferred type: ";
        }
        if (typeString != null) {
          myBody.addItem(combUp(typeString));
          PythonDocumentationProvider.describeTypeWithLinks(
              myBody, elementDefinition, type, context);
        }
      }
    }
  }
  public String build() {
    final TypeEvalContext context =
        TypeEvalContext.userInitiated(myElement.getProject(), myElement.getContainingFile());
    final PsiElement outerElement =
        myOriginalElement != null ? myOriginalElement.getParent() : null;

    final PsiElement elementDefinition = resolveToDocStringOwner();
    final boolean isProperty = buildFromProperty(elementDefinition, outerElement, context);

    if (myProlog.isEmpty() && !isProperty && !isAttribute()) {
      myProlog.add(myReassignmentChain);
    }

    if (elementDefinition instanceof PyDocStringOwner) {
      buildFromDocstring(elementDefinition, isProperty);
    } else if (isAttribute()) {
      buildFromAttributeDoc();
    } else if (elementDefinition instanceof PyNamedParameter) {
      buildFromParameter(context, outerElement, elementDefinition);
    } else if (elementDefinition != null && outerElement instanceof PyReferenceExpression) {
      myBody.addItem(combUp("\nInferred type: "));
      PythonDocumentationProvider.describeExpressionTypeWithLinks(
          myBody, (PyReferenceExpression) outerElement, context);
    }

    if (elementDefinition != null
        && PythonDialectsTokenSetProvider.INSTANCE
            .getKeywordTokens()
            .contains(elementDefinition.getNode().getElementType())) {
      buildForKeyword(elementDefinition.getText());
    }
    if (myBody.isEmpty() && myEpilog.isEmpty()) {
      return null; // got nothing substantial to say!
    } else {
      return myResult.toString();
    }
  }
  private boolean buildFromProperty(
      PsiElement elementDefinition,
      @Nullable final PsiElement outerElement,
      @NotNull final TypeEvalContext context) {
    if (myOriginalElement == null) {
      return false;
    }
    final String elementName = myOriginalElement.getText();
    if (!PyNames.isIdentifier(elementName)) {
      return false;
    }
    if (!(outerElement instanceof PyQualifiedExpression)) {
      return false;
    }
    final PyExpression qualifier = ((PyQualifiedExpression) outerElement).getQualifier();
    if (qualifier == null) {
      return false;
    }
    final PyType type = context.getType(qualifier);
    if (!(type instanceof PyClassType)) {
      return false;
    }
    final PyClass cls = ((PyClassType) type).getPyClass();
    final Property property = cls.findProperty(elementName, true, null);
    if (property == null) {
      return false;
    }

    final AccessDirection direction = AccessDirection.of((PyElement) outerElement);
    final Maybe<PyCallable> accessor = property.getByDirection(direction);
    myProlog
        .addItem("property ")
        .addWith(TagBold, $().addWith(TagCode, $(elementName)))
        .addItem(" of ")
        .add(PythonDocumentationProvider.describeClass(cls, TagCode, true, true));
    if (accessor.isDefined() && property.getDoc() != null) {
      myBody.addItem(": ").addItem(property.getDoc()).addItem(BR);
    } else {
      final PyCallable getter = property.getGetter().valueOrNull();
      if (getter != null && getter != myElement && getter instanceof PyFunction) {
        // not in getter, getter's doc comment may be useful
        final PyStringLiteralExpression docstring = ((PyFunction) getter).getDocStringExpression();
        if (docstring != null) {
          myProlog
              .addItem(BR)
              .addWith(TagItalic, $("Copied from getter:"))
              .addItem(BR)
              .addItem(docstring.getStringValue());
        }
      }
      myBody.addItem(BR);
    }
    myBody.addItem(BR);
    if (accessor.isDefined() && accessor.value() == null) elementDefinition = null;
    final String accessorKind = getAccessorKind(direction);
    if (elementDefinition != null) {
      myEpilog.addWith(TagSmall, $(BR, BR, accessorKind, " of property")).addItem(BR);
    }

    if (!(elementDefinition instanceof PyDocStringOwner)) {
      myBody
          .addWith(
              TagItalic,
              elementDefinition != null ? $("Declaration: ") : $(accessorKind + " is not defined."))
          .addItem(BR);
      if (elementDefinition != null) {
        myBody.addItem(combUp(PyUtil.getReadableRepr(elementDefinition, false)));
      }
    }
    return true;
  }