/******************************************************************************* * Copyright (c) 2009, 2013, 2014, 2015, 2016, 2017 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Zend Technologies * Dawid PakuĊ‚a [418890] *******************************************************************************/ package org.eclipse.php.internal.ui.documentation; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.dltk.annotations.Nullable; import org.eclipse.dltk.ast.references.TypeReference; import org.eclipse.dltk.ast.references.VariableReference; import org.eclipse.dltk.core.*; import org.eclipse.dltk.internal.core.util.MethodOverrideTester; import org.eclipse.dltk.ui.ScriptElementLabels; import org.eclipse.php.core.compiler.PHPFlags; import org.eclipse.php.core.compiler.ast.nodes.PHPDocBlock; import org.eclipse.php.core.compiler.ast.nodes.PHPDocTag; import org.eclipse.php.core.compiler.ast.nodes.PHPDocTag.TagKind; import org.eclipse.php.internal.core.Constants; import org.eclipse.php.internal.core.typeinference.FakeConstructor; import org.eclipse.php.internal.core.typeinference.PHPModelUtils; import org.eclipse.php.internal.core.util.MagicMemberUtil; import org.eclipse.php.internal.core.util.MagicMemberUtil.MagicField; import org.eclipse.php.internal.core.util.MagicMemberUtil.MagicMember; import org.eclipse.php.internal.core.util.MagicMemberUtil.MagicMethod; import org.eclipse.php.internal.ui.PHPUiPlugin; import org.eclipse.php.internal.ui.corext.util.SuperTypeHierarchyCache; import org.eclipse.text.edits.ReplaceEdit; /** * Helper to get the content of a Javadoc comment as HTML. * * <p> * <strong>This is work in progress. Parts of this will later become API through * {@link JavadocContentAccess}</strong> * </p> * * @since 3.4 */ @SuppressWarnings({ "unchecked", "rawtypes" }) public class PHPDocumentationContentAccess { private static final Pattern INLINE_LINK_PATTERN = Pattern.compile("\\{@link[\\p{javaWhitespace}]+[^\\}]*\\}"); //$NON-NLS-1$ private static final String BLOCK_TAG_START = "<dl>"; //$NON-NLS-1$ private static final String BLOCK_TAG_END = "</dl>"; //$NON-NLS-1$ private static final String BlOCK_TAG_ENTRY_START = "<dd>"; //$NON-NLS-1$ private static final String BlOCK_TAG_ENTRY_END = "</dd>"; //$NON-NLS-1$ private static final String PARAM_NAME_START = "<b>"; //$NON-NLS-1$ private static final String PARAM_NAME_END = "</b> "; //$NON-NLS-1$ private static final String PARAM_RETURN_START = "<b>"; //$NON-NLS-1$ private static final String PARAM_RETURN_END = "</b> "; //$NON-NLS-1$ private static final String PARAM_THROWS_START = "<b>"; //$NON-NLS-1$ private static final String PARAM_THROWS_END = "</b>"; //$NON-NLS-1$ private static final int PARAMETER_TYPE_TYPE = 1; private static final int PARAMETER_NAME_TYPE = 2; private static final int PARAMETER_DESCRIPTION_TYPE = 3; private static final int RETURN_TYPE_TYPE = 4; private static final int RETURN_DESCRIPTION_TYPE = 5; /** * Implements the "Algorithm for Inheriting Method Comments" as specified * for <a href= * "http://java.sun.com/j2se/1.4.2/docs/tooldocs/solaris/javadoc.html#inheritingcomments" * >1.4.2</a>, <a href= * "http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/javadoc.html#inheritingcomments" * >1.5</a>, and <a href= * "http://java.sun.com/javase/6/docs/technotes/tools/windows/javadoc.html#inheritingcomments" * >1.6</a>. * * <p> * Unfortunately, the implementation is broken in Javadoc implementations * since 1.5, see * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6376959">Sun' * s bug</a>. * </p> * * <p> * We adhere to the spec. * </p> */ private static abstract class InheritDocVisitor { public static final Object STOP_BRANCH = new Object() { @Override public String toString() { return "STOP_BRANCH"; //$NON-NLS-1$ } }; public static final Object CONTINUE = new Object() { @Override public String toString() { return "CONTINUE"; //$NON-NLS-1$ } }; /** * Visits a type and decides how the visitor should proceed. * * @param currType * the current type * @return * <ul> * <li>{@link #STOP_BRANCH} to indicate that no Javadoc has been * found and visiting super types should stop here</li> * <li>{@link #CONTINUE} to indicate that no Javadoc has been * found and visiting super types should continue</li> * <li>an {@link Object} or <code>null</code>, to indicate that * visiting should be cancelled immediately. The returned value * is the result of * {@link #visitInheritDoc(IType, ITypeHierarchy)}</li> * </ul> * @throws ModelException * unexpected problem * @see #visitInheritDoc(IType, ITypeHierarchy) */ public abstract Object visit(IType currType) throws ModelException; /** * Visits the super types of the given <code>currentType</code>. * * @param currentType * the starting type * @param typeHierarchy * a super type hierarchy that contains * <code>currentType</code> * @return the result from a call to {@link #visit(IType)}, or * <code>null</code> if none of the calls returned a result * @throws ModelException * unexpected problem */ public Object visitInheritDoc(IType currentType, ITypeHierarchy typeHierarchy) throws ModelException { ArrayList visited = new ArrayList(); visited.add(currentType); // Object result = visitInheritDocInterfaces(visited, currentType, // typeHierarchy); // if (result != InheritDocVisitor.CONTINUE) // return result; Object result; IType[] superClasses = typeHierarchy.getSuperclass(currentType); for (IType superClass : superClasses) { while (superClass != null && !visited.contains(superClass)) { result = visit(superClass); if (result == InheritDocVisitor.STOP_BRANCH) { return null; } else if (result == InheritDocVisitor.CONTINUE) { visited.add(superClass); result = visitInheritDocInterfaces(visited, superClass, typeHierarchy); if (result != InheritDocVisitor.CONTINUE) return result; else superClasses = typeHierarchy.getSuperclass(superClass); } else { return result; } } } return null; } /** * Visits the super interfaces of the given type in the given hierarchy, * thereby skipping already visited types. * * @param visited * set of visited types * @param currentType * type whose super interfaces should be visited * @param typeHierarchy * type hierarchy (must include <code>currentType</code>) * @return the result, or {@link #CONTINUE} if no result has been found * @throws ModelException * unexpected problem */ private Object visitInheritDocInterfaces(ArrayList visited, IType currentType, ITypeHierarchy typeHierarchy) throws ModelException { ArrayList toVisitChildren = new ArrayList(); IType[] superInterfaces = typeHierarchy.getSuperclass(currentType); for (int i = 0; i < superInterfaces.length; i++) { IType superInterface = superInterfaces[i]; if (visited.contains(superInterface)) continue; visited.add(superInterface); Object result = visit(superInterface); if (result == InheritDocVisitor.STOP_BRANCH) { // skip } else if (result == InheritDocVisitor.CONTINUE) { toVisitChildren.add(superInterface); } else { return result; } } for (Iterator iter = toVisitChildren.iterator(); iter.hasNext();) { IType child = (IType) iter.next(); Object result = visitInheritDocInterfaces(visited, child, typeHierarchy); if (result != InheritDocVisitor.CONTINUE) return result; } return InheritDocVisitor.CONTINUE; } } private static class JavadocLookup { private static final JavadocLookup NONE = new JavadocLookup(null) { @Override public CharSequence getInheritedMainDescription(IMethod method) { return null; } @Override public CharSequence getInheritedParamDescription(IMethod method, int i) { return null; } @Override public CharSequence getInheritedReturnDescription(IMethod method) { return null; } @Override public CharSequence getInheritedExceptionDescription(IMethod method, String name) { return null; } @Override public List<PHPDocTag> getInheritedExceptions(IMethod method) { return null; } }; private static interface DescriptionGetter { /** * Returns a Javadoc tag description or <code>null</code>. * * @param contentAccess * the content access * @return the description, or <code>null</code> if none * @throws ModelException * unexpected problem */ Object getDescription(PHPDocumentationContentAccess contentAccess) throws ModelException; } private final IType fStartingType; private final HashMap fContentAccesses; private ITypeHierarchy fTypeHierarchy; private MethodOverrideTester fOverrideTester; private JavadocLookup(IType startingType) { fStartingType = startingType; fContentAccesses = new HashMap(); } /** * For the given method, returns the main description from an overridden * method. * * @param method * a method * @return the description that replaces the * <code>{@inheritDoc}</code> tag, or <code>null</code> if * none could be found */ public CharSequence getInheritedMainDescription(IMethod method) { return (CharSequence) getInheritedDescription(method, new DescriptionGetter() { @Override public Object getDescription(PHPDocumentationContentAccess contentAccess) { return contentAccess.getMainDescription(); } }); } /** * For the given method, returns the @param tag description for the * given parameter from an overridden method. * * @param method * a method * @param paramIndex * the index of the parameter * @return the description that replaces the * <code>{@inheritDoc}</code> tag, or <code>null</code> if * none could be found */ public CharSequence getInheritedParamDescription(IMethod method, final int paramIndex) { return (CharSequence) getInheritedDescription(method, contentAccess -> contentAccess.getInheritedParamDescription(paramIndex)); } /** * For the given method, returns the @param tag description for the * given parameter from an overridden method. * * @param method * a method * @param paramIndex * the index of the parameter * @return the description that replaces the * <code>{@inheritDoc}</code> tag, or <code>null</code> if * none could be found */ public CharSequence getInheritedParamType(IMethod method, final int paramIndex) { return (CharSequence) getInheritedDescription(method, new DescriptionGetter() { @Override public Object getDescription(PHPDocumentationContentAccess contentAccess) throws ModelException { return contentAccess.getInheritedParamType(paramIndex); } }); } /** * For the given method, returns the @return tag description from an * overridden method. * * @param method * a method * @return the description that replaces the * <code>{@inheritDoc}</code> tag, or <code>null</code> if * none could be found */ public CharSequence getInheritedReturnDescription(IMethod method) { return (CharSequence) getInheritedDescription(method, new DescriptionGetter() { @Override public Object getDescription(PHPDocumentationContentAccess contentAccess) { return contentAccess.getReturnDescription(); } }); } /** * For the given method, returns the @throws/@exception tag description * for the given exception from an overridden method. * * @param method * a method * @param simpleName * the simple name of an exception * @return the description that replaces the * <code>{@inheritDoc}</code> tag, or <code>null</code> if * none could be found */ public CharSequence getInheritedExceptionDescription(IMethod method, final String simpleName) { return (CharSequence) getInheritedDescription(method, new DescriptionGetter() { @Override public Object getDescription(PHPDocumentationContentAccess contentAccess) { return contentAccess.getExceptionDescription(simpleName); } }); } /** * For the given method, returns all the @throws/@exception tags from an * overridden method. * * @param method * a method * @return all the exceptions that replace the * <code>{@inheritDoc}</code> tag, or <code>null</code> if * none could be found */ public List<PHPDocTag> getInheritedExceptions(IMethod method) { return (List<PHPDocTag>) getInheritedDescription(method, new DescriptionGetter() { @Override public Object getDescription(PHPDocumentationContentAccess contentAccess) { return contentAccess.getExceptions(); } }); } private Object getInheritedDescription(final IMethod method, final DescriptionGetter descriptionGetter) { try { return new InheritDocVisitor() { @Override public Object visit(IType currType) throws ModelException { IMethod overridden = getOverrideTester().findOverriddenMethodInType(currType, method); if (overridden == null) return InheritDocVisitor.CONTINUE; PHPDocumentationContentAccess contentAccess = getJavadocContentAccess(overridden); if (contentAccess == null) { // if (overridden.getOpenable().getBuffer() == null) // { // return InheritDocVisitor.CONTINUE; // } else { // return InheritDocVisitor.CONTINUE; // } return InheritDocVisitor.CONTINUE; } Object overriddenDescription = descriptionGetter.getDescription(contentAccess); if (overriddenDescription != null) return overriddenDescription; else return InheritDocVisitor.CONTINUE; } }.visitInheritDoc(method.getDeclaringType(), getTypeHierarchy()); } catch (ModelException e) { PHPUiPlugin.log(e); } return null; } /** * @param method * the method * @return the Javadoc content access for the given method, or * <code>null</code> if no Javadoc could be found in source * @throws ModelException * unexpected problem */ private PHPDocumentationContentAccess getJavadocContentAccess(IMethod method) throws ModelException { Object cached = fContentAccesses.get(method); if (cached != null) return (PHPDocumentationContentAccess) cached; if (fContentAccesses.containsKey(method)) return null; // IBuffer buf = method.getOpenable().getBuffer(); // if (buf == null) { // no source attachment found // fContentAccesses.put(method, null); // return null; // } PHPDocBlock javadoc = PHPModelUtils.getDocBlock(method); if (javadoc == null) { fContentAccesses.put(method, null); return null; } PHPDocumentationContentAccess contentAccess = new PHPDocumentationContentAccess(method, javadoc, this); fContentAccesses.put(method, contentAccess); return contentAccess; } private ITypeHierarchy getTypeHierarchy() throws ModelException { if (fTypeHierarchy == null) fTypeHierarchy = SuperTypeHierarchyCache.getTypeHierarchy(fStartingType); return fTypeHierarchy; } private MethodOverrideTester getOverrideTester() throws ModelException { if (fOverrideTester == null) fOverrideTester = SuperTypeHierarchyCache.getMethodOverrideTester(fStartingType); return fOverrideTester; } } private final IMember fMember; /** * The method, or <code>null</code> if {@link #fMember} is not a method * where {@inheritDoc} could work. */ private final IMethod fMethod; private final PHPDocBlock fJavadoc; private final JavadocLookup fJavadocLookup; private StringBuilder fBuf; private StringBuilder fMainDescription; private StringBuilder fReturnDescription; private StringBuilder[] fParamDescriptions; private StringBuilder[] fParamTypes; private HashMap<String, StringBuilder> fExceptionDescriptions; private List<PHPDocTag> fExceptions; private PHPDocumentationContentAccess(IMethod method, PHPDocBlock javadoc, JavadocLookup lookup) { fMember = method; fMethod = method; fJavadoc = javadoc; fJavadocLookup = lookup; } private PHPDocumentationContentAccess(IMember member, PHPDocBlock javadoc) { fMember = member; fMethod = null; fJavadoc = javadoc; fJavadocLookup = JavadocLookup.NONE; } /** * Gets an IMember's Javadoc comment content from the source or Javadoc * attachment and renders the tags and links in HTML. Returns * <code>null</code> if the member does not contain a Javadoc comment or if * no source is available. * * @param member * the member to get the Javadoc of * @param useAttachedJavadoc * if <code>true</code> Javadoc will be extracted from attached * Javadoc if there's no source * @return the Javadoc comment content in HTML or <code>null</code> if the * member does not have a Javadoc comment or if no source is * available * @throws ModelException * is thrown when the element's Javadoc can not be accessed */ public static String getHTMLContent(IMember member) throws ModelException { return getHTMLContentFromSource(member); } private static StringBuilder createSuperMethodReferences(final IMethod method) throws ModelException { IType type = method.getDeclaringType(); ITypeHierarchy hierarchy = SuperTypeHierarchyCache.getTypeHierarchy(type); final MethodOverrideTester tester = SuperTypeHierarchyCache.getMethodOverrideTester(type); final ArrayList<IMethod> superInterfaceMethods = new ArrayList<>(); final IMethod[] superClassMethod = { null }; new InheritDocVisitor() { @Override public Object visit(IType currType) throws ModelException { IMethod overridden = tester.findOverriddenMethodInType(currType, method); if (overridden == null) return InheritDocVisitor.CONTINUE; if (PHPFlags.isInterface(currType.getFlags())) superInterfaceMethods.add(overridden); else superClassMethod[0] = overridden; return STOP_BRANCH; } }.visitInheritDoc(type, hierarchy); boolean hasSuperInterfaceMethods = superInterfaceMethods.size() != 0; if (!hasSuperInterfaceMethods && superClassMethod[0] == null) return null; StringBuilder buf = new StringBuilder(); buf.append("<div>"); //$NON-NLS-1$ if (hasSuperInterfaceMethods) { buf.append("<b>"); //$NON-NLS-1$ buf.append(PHPDocumentationMessages.JavaDoc2HTMLTextReader_specified_by_section); buf.append("</b> "); //$NON-NLS-1$ for (Iterator<IMethod> iter = superInterfaceMethods.iterator(); iter.hasNext();) { IMethod overridden = (IMethod) iter.next(); buf.append(createMethodInTypeLinks(overridden)); if (iter.hasNext()) buf.append(ScriptElementLabels.COMMA_STRING); } } if (superClassMethod[0] != null) { if (hasSuperInterfaceMethods) buf.append(ScriptElementLabels.COMMA_STRING); buf.append("<b>"); //$NON-NLS-1$ buf.append(PHPDocumentationMessages.JavaDoc2HTMLTextReader_overrides_section); buf.append("</b> "); //$NON-NLS-1$ buf.append(createMethodInTypeLinks(superClassMethod[0])); } buf.append("</div>"); //$NON-NLS-1$ return buf; } private static String createMethodInTypeLinks(IMethod overridden) { CharSequence methodLink = createSimpleMemberLink(overridden); CharSequence typeLink = createSimpleMemberLink(overridden.getDeclaringType()); String methodInType = MessageFormat.format(PHPDocumentationMessages.JavaDoc2HTMLTextReader_method_in_type, new Object[] { methodLink, typeLink }); return methodInType; } private static CharSequence createSimpleMemberLink(IMember member) { StringBuffer buf = new StringBuffer(); buf.append("<a href='"); //$NON-NLS-1$ try { String uri = PHPElementLinks.createURI(PHPElementLinks.PHPDOC_SCHEME, member); buf.append(uri); } catch (URISyntaxException e) { PHPUiPlugin.log(e); } buf.append("'>"); //$NON-NLS-1$ ScriptElementLabels.getDefault().getElementLabel(member, 0, buf); buf.append("</a>"); //$NON-NLS-1$ return buf; } private static String getHTMLContentFromSource(IMember member) throws ModelException { return javadoc2HTML(member); } private static PHPDocBlock getJavadocNode(IMember member) { if (member instanceof IType) { return PHPModelUtils.getDocBlock((IType) member); } if (member instanceof IMethod) { PHPDocBlock result = PHPModelUtils.getDocBlock((IMethod) member); if (result == null && member instanceof FakeConstructor) { FakeConstructor fc = (FakeConstructor) member; result = PHPModelUtils.getDocBlock((IType) fc.getParent()); } return result; } if (member instanceof IField) { return PHPModelUtils.getDocBlock((IField) member); } return null; } private static String javadoc2HTML(IMember member) { PHPDocBlock javadoc = getJavadocNode(member); if (javadoc == null) { MagicMember magicMember = getMagicMember(member); if (magicMember != null) { return getHTMLForMagicMember(member, magicMember); } javadoc = new PHPDocBlock(0, 0, null, null, new PHPDocTag[0]); } if (canInheritJavadoc(member)) { IMethod method = (IMethod) member; IType declaringType = method.getDeclaringType(); JavadocLookup lookup; if (declaringType == null) { lookup = JavadocLookup.NONE; } else { lookup = new JavadocLookup(method.getDeclaringType()); } return new PHPDocumentationContentAccess(method, javadoc, lookup).toHTML(); } return new PHPDocumentationContentAccess(member, javadoc).toHTML(); } private static String getHTMLForMagicMember(IMember member, MagicMember magicMember) { StringBuilder fBuf = new StringBuilder(); if (appendBuiltinDoc(member, fBuf)) { return fBuf.toString(); } if (magicMember instanceof MagicField) { MagicField magicField = (MagicField) magicMember; fBuf.append(magicField.desc); fBuf.append(BLOCK_TAG_START); fBuf.append("<dt>"); //$NON-NLS-1$ fBuf.append("Type"); //$NON-NLS-1$ fBuf.append("</dt>"); //$NON-NLS-1$ fBuf.append(BlOCK_TAG_ENTRY_START); fBuf.append(" "); //$NON-NLS-1$ fBuf.append(magicField.type); fBuf.append(BlOCK_TAG_ENTRY_END); fBuf.append(BLOCK_TAG_END); } else { MagicMethod magicMethod = (MagicMethod) magicMember; fBuf.append(magicMethod.desc); fBuf.append(BLOCK_TAG_START); if (magicMethod.parameterNames != null && magicMethod.parameterNames.length > 0) { fBuf.append("<dt>"); //$NON-NLS-1$ fBuf.append(PHPDocumentationMessages.JavaDoc2HTMLTextReader_parameters_section); fBuf.append("</dt>"); //$NON-NLS-1$ for (int i = 0; i < magicMethod.parameterNames.length; i++) { fBuf.append(BlOCK_TAG_ENTRY_START); fBuf.append(" "); //$NON-NLS-1$ String parameterName = magicMethod.parameterNames[i]; String parameterType = magicMethod.parameterTypes[i]; if (parameterType != null) { fBuf.append(PARAM_NAME_START); fBuf.append(parameterType); fBuf.append(PARAM_NAME_END); } fBuf.append(PARAM_NAME_START); fBuf.append(parameterName); fBuf.append(PARAM_NAME_END); fBuf.append(BlOCK_TAG_ENTRY_END); } } if (magicMethod.returnType != null) { fBuf.append("<dt>"); //$NON-NLS-1$ fBuf.append(PHPDocumentationMessages.JavaDoc2HTMLTextReader_returns_section); fBuf.append("</dt>"); //$NON-NLS-1$ fBuf.append(BlOCK_TAG_ENTRY_START); fBuf.append(" "); //$NON-NLS-1$ fBuf.append(PARAM_RETURN_START); fBuf.append(magicMethod.returnType); fBuf.append(PARAM_RETURN_END); fBuf.append(BlOCK_TAG_ENTRY_END); } fBuf.append(BLOCK_TAG_END); } return fBuf.toString(); } private static MagicMember getMagicMember(IMember member) { if (!(member instanceof IMethod || member instanceof IField)) { return null; } IType type = member.getDeclaringType(); PHPDocBlock doc = PHPModelUtils.getDocBlock(type); if (doc == null) { return null; } Pattern WHITESPACE_SEPERATOR = MagicMemberUtil.WHITESPACE_SEPERATOR; final PHPDocTag[] tags = doc.getTags(); for (PHPDocTag docTag : tags) { final TagKind tagKind = docTag.getTagKind(); if (member instanceof IMethod && tagKind == TagKind.METHOD) { // http://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumentor/tutorial_tags.method.pkg.html String docTagValue = docTag.getValue().trim(); int index = docTagValue.indexOf('('); // $NON-NLS-1$ if (index != -1) { String[] split = WHITESPACE_SEPERATOR.split(docTagValue.substring(0, index).trim()); if (split.length == 1) { docTagValue = new StringBuilder(MagicMemberUtil.VOID_RETURN_TYPE).append(Constants.SPACE) .append(docTagValue).toString(); } else if (split.length == 2 && Constants.STATIC.equals(split[0])) { StringBuilder sb = new StringBuilder(Constants.STATIC); sb.append(Constants.SPACE).append(MagicMemberUtil.VOID_RETURN_TYPE); sb.append(docTagValue.substring(6)); docTagValue = sb.toString(); } } String[] split = WHITESPACE_SEPERATOR.split(docTagValue); if (split.length < 2) { continue; } if (Constants.STATIC.equals(split[0])) { if (split.length < 3) { break; } split = Arrays.copyOfRange(split, 1, split.length); docTagValue = docTagValue.substring(7); } if (MagicMemberUtil.removeParenthesis(split).equals(member.getElementName())) { return MagicMemberUtil.getMagicMethod(docTagValue); } else if (MagicMemberUtil.removeParenthesis2(split).equals(member.getElementName())) { return MagicMemberUtil.getMagicMethod2(docTagValue); } } else if (member instanceof IField) { // http://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumentor/tutorial_tags.property.pkg.html final MagicField magicField = MagicMemberUtil.getMagicPropertiesField(docTag); if (magicField == null) { // it's not a @property, @property-read or @property-write // tag continue; } if (member.getElementName().equals(magicField.name)) { return magicField; } } } return null; } private static boolean canInheritJavadoc(IMember member) { if (member instanceof IMethod && member.getScriptProject().exists()) { /* * Exists test catches ExternalJavaProject, in which case no * hierarchy can be built. */ try { return !((IMethod) member).isConstructor(); } catch (ModelException e) { PHPUiPlugin.log(e); } } return false; } private String toHTML() { fBuf = new StringBuilder(); if (appendBuiltinDoc(fMember, fBuf)) { return fBuf.toString(); } // After first loop, non-null entries in the following two lists are // missing and need to be inherited: List<String> parameterNames = initParameterNames(); List<String> exceptionNames = new ArrayList<>(); PHPDocTag deprecatedTag = null; PHPDocTag returnTag = null; PHPDocTag namespaceTag = null; List<PHPDocTag> parameters = new ArrayList<>(); List<PHPDocTag> exceptions = new ArrayList<>(); List<PHPDocTag> versions = new ArrayList<>(); List<PHPDocTag> authors = new ArrayList<>(); List<PHPDocTag> sees = new ArrayList<>(); List<PHPDocTag> since = new ArrayList<>(); List<PHPDocTag> rest = new ArrayList<>(); String shortDescription = fJavadoc.getShortDescription(); String longDescription = fJavadoc.getLongDescription(); PHPDocTag[] tags = fJavadoc.getTags(); for (PHPDocTag tag : tags) { if (TagKind.PARAM == tag.getTagKind()) { parameters.add(tag); if (!tag.isValidParamTag()) { if (parameterNames.size() > parameters.indexOf(tag)) parameterNames.set(parameters.indexOf(tag), null); } else { int paramIndex = parameterNames.indexOf(tag.getVariableReference().getName()); if (paramIndex != -1) { parameterNames.set(paramIndex, null); } } } else if (TagKind.RETURN == tag.getTagKind()) { if (returnTag == null) returnTag = tag; // the Javadoc tool only shows the first // return tag } else if (TagKind.NAMESPACE == tag.getTagKind()) { if (namespaceTag == null) namespaceTag = tag; } else if (TagKind.THROWS == tag.getTagKind()) { exceptions.add(tag); List<TypeReference> fragments = tag.getTypeReferences(); if (fragments.size() > 0) { exceptionNames.add(fragments.get(0).getName()); } } else if (TagKind.SINCE == tag.getTagKind()) { since.add(tag); } else if (TagKind.VERSION == tag.getTagKind()) { versions.add(tag); } else if (TagKind.AUTHOR == tag.getTagKind()) { authors.add(tag); } else if (TagKind.SEE == tag.getTagKind()) { sees.add(tag); } else if (TagKind.DEPRECATED == tag.getTagKind()) { if (deprecatedTag == null) deprecatedTag = tag; // the Javadoc tool only shows the // first deprecated tag } else { rest.add(tag); } } if (deprecatedTag != null) handleDeprecatedTag(deprecatedTag); boolean hasShortDescription = shortDescription != null && shortDescription.length() > 0; if (hasShortDescription) fBuf.append(shortDescription); if (longDescription != null && longDescription.length() > 0) { fBuf.append("<p>"); //$NON-NLS-1$ longDescription = longDescription.replaceAll("(\r\n){2,}|\n{2,}|\r{2,}", "</p><p>"); //$NON-NLS-1$ //$NON-NLS-2$ fBuf.append(longDescription); fBuf.append("</p>"); //$NON-NLS-1$ } else if (fMethod != null && !hasShortDescription) { CharSequence inherited = fJavadocLookup.getInheritedMainDescription(fMethod); handleInherited(inherited); } handleInlineLinks(); CharSequence[] parameterDescriptions = new CharSequence[parameterNames.size()]; CharSequence[] parameterTypes = new CharSequence[parameterNames.size()]; boolean hasInheritedParameters = inheritParameterDescriptions(parameterNames, parameterDescriptions, parameterTypes); boolean hasParameters = parameters.size() > 0 || hasInheritedParameters; CharSequence returnDescription = null; if (returnTag == null) returnDescription = fJavadocLookup.getInheritedReturnDescription(fMethod); boolean hasReturnTag = returnTag != null || returnDescription != null; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=418890 // http://phpdoc.org/docs/latest/guides/inheritance.html if (fMethod != null && exceptions.size() == 0) { List<PHPDocTag> inheritedExceptions = fJavadocLookup.getInheritedExceptions(fMethod); if (inheritedExceptions != null) { exceptions.addAll(inheritedExceptions); } } CharSequence[] exceptionDescriptions = new CharSequence[exceptionNames.size()]; boolean hasInheritedExceptions = inheritExceptionDescriptions(exceptionNames, exceptionDescriptions); boolean hasExceptions = exceptions.size() > 0 || hasInheritedExceptions; boolean hasNamespace = namespaceTag != null; if (hasParameters || hasReturnTag || hasExceptions || hasNamespace || versions.size() > 0 || authors.size() > 0 || since.size() > 0 || sees.size() > 0 || rest.size() > 0 || (fBuf.length() > 0 && (parameterDescriptions.length > 0 || exceptionDescriptions.length > 0))) { handleSuperMethodReferences(); fBuf.append(BLOCK_TAG_START); handleParameterTags(parameters, parameterNames, parameterTypes, parameterDescriptions); handleReturnTag(returnTag, returnDescription); handleNamespaceTag(namespaceTag); handleExceptionTags(exceptions, exceptionNames, exceptionDescriptions); handleBlockTags(PHPDocumentationMessages.JavaDoc2HTMLTextReader_since_section, since); handleBlockTags(PHPDocumentationMessages.JavaDoc2HTMLTextReader_version_section, versions); handleBlockTags(PHPDocumentationMessages.JavaDoc2HTMLTextReader_author_section, authors); handleBlockTags(PHPDocumentationMessages.JavaDoc2HTMLTextReader_see_section, sees); handleBlockTags(rest); fBuf.append(BLOCK_TAG_END); } else if (fBuf.length() > 0) { handleSuperMethodReferences(); } String result = fBuf.toString(); fBuf = null; return result; } private void handleInlineLinks() { Matcher m = INLINE_LINK_PATTERN.matcher(fBuf); List<ReplaceEdit> replaceLinks = new ArrayList<>(); while (m.find()) { String[] strs = m.group().split("[\\p{javaWhitespace}]+", 3); //$NON-NLS-1$ String url = removeLastRightCurlyBrace(strs[1]); String link = "";//$NON-NLS-1$ if (url.toLowerCase().startsWith("http")) { //$NON-NLS-1$ String description = ""; //$NON-NLS-1$ if (strs.length == 3) { description = removeLastRightCurlyBrace(strs[2]); } else { description = url; } link = String.format("<a href=\"%s\">%s</a>", url, description); //$NON-NLS-1$ } else { link = handleLinks(Arrays.asList(new TypeReference(0, 0, url))).toString(); } replaceLinks.add(new ReplaceEdit(m.start(), m.end() - m.start(), link)); } for (int i = replaceLinks.size() - 1; i >= 0; i--) { ReplaceEdit replaceLink = replaceLinks.get(i); fBuf.replace(replaceLink.getOffset(), replaceLink.getOffset() + replaceLink.getLength(), replaceLink.getText()); } } private String removeLastRightCurlyBrace(String str) { if (str.endsWith("}")) { //$NON-NLS-1$ return str.substring(0, str.length() - 1); } return str; } private void handleDeprecatedTag(PHPDocTag tag) { fBuf.append("<p><b>"); //$NON-NLS-1$ fBuf.append(PHPDocumentationMessages.JavaDoc2HTMLTextReader_deprecated_section); fBuf.append("</b> <i>"); //$NON-NLS-1$ handleContentElements(tag); fBuf.append("</i><p>"); //$NON-NLS-1$ } private void handleSuperMethodReferences() { if (fMethod != null && fMethod.getDeclaringType() != null) { try { StringBuilder superMethodReferences = createSuperMethodReferences(fMethod); if (superMethodReferences != null) fBuf.append(superMethodReferences); } catch (ModelException e) { PHPUiPlugin.log(e); } } } private List<String> initParameterNames() { if (fMethod != null) { try { List<String> list = new ArrayList<>(Arrays.asList(fMethod.getParameterNames())); if (PHPFlags.isVariadic(fMethod.getFlags()) && !list.isEmpty()) { int lastIndex = list.size() - 1; String name = list.get(lastIndex); if (name != null) { list.set(lastIndex, ScriptElementLabels.ELLIPSIS_STRING + name); } } return list; } catch (ModelException e) { PHPUiPlugin.log(e); } } return Collections.EMPTY_LIST; } private boolean inheritParameterDescriptions(List<String> parameterNames, CharSequence[] parameterDescriptions, CharSequence[] parameterTypes) { boolean hasInheritedParameters = false; if (fMethod != null && fMethod.getDeclaringType() == null) { return hasInheritedParameters; } for (int i = 0; i < parameterNames.size(); i++) { String name = (String) parameterNames.get(i); if (name != null) { parameterDescriptions[i] = fJavadocLookup.getInheritedParamDescription(fMethod, i); parameterTypes[i] = fJavadocLookup.getInheritedParamType(fMethod, i); if (parameterDescriptions[i] != null) hasInheritedParameters = true; } } return hasInheritedParameters; } private boolean inheritExceptionDescriptions(List<String> exceptionNames, CharSequence[] exceptionDescriptions) { boolean hasInheritedExceptions = false; if (fMethod != null && fMethod.getDeclaringType() == null) { return hasInheritedExceptions; } for (int i = 0; i < exceptionNames.size(); i++) { String name = (String) exceptionNames.get(i); if (name != null) { exceptionDescriptions[i] = fJavadocLookup.getInheritedExceptionDescription(fMethod, name); if (exceptionDescriptions[i] != null) hasInheritedExceptions = true; } } return hasInheritedExceptions; } CharSequence getMainDescription() { if (fMainDescription == null) { fMainDescription = new StringBuilder(); fBuf = fMainDescription; String shortDescription = fJavadoc.getShortDescription(); if (shortDescription != null && shortDescription.length() > 0) { fMainDescription.append(shortDescription); } fBuf = null; } return fMainDescription.length() > 0 ? fMainDescription : null; } CharSequence getReturnDescription() { if (fReturnDescription == null) { fReturnDescription = new StringBuilder(); fBuf = fReturnDescription; for (PHPDocTag tag : fJavadoc.getTags(TagKind.RETURN)) { handleReturnTag(tag); break; } fBuf = null; } return fReturnDescription.length() > 0 ? fReturnDescription : null; } CharSequence getInheritedParamDescription(int paramIndex) throws ModelException { if (fMethod != null) { String[] parameterNames = fMethod.getParameterNames(); if (paramIndex >= parameterNames.length) { return null; } if (fParamDescriptions == null) { fParamDescriptions = new StringBuilder[parameterNames.length]; } else { StringBuilder description = fParamDescriptions[paramIndex]; if (description != null) { return description.length() > 0 ? description : null; } } StringBuilder description = new StringBuilder(); fParamDescriptions[paramIndex] = description; fBuf = description; String paramName = parameterNames[paramIndex]; String info = getParameterInfo(fJavadoc, paramName, PARAMETER_DESCRIPTION_TYPE); if (info != null) description.append(info); fBuf = null; return description.length() > 0 ? description : null; } return null; } CharSequence getInheritedParamType(int paramIndex) throws ModelException { if (fMethod != null) { String[] parameterNames = fMethod.getParameterNames(); if (paramIndex >= parameterNames.length) { return null; } if (fParamTypes == null) { fParamTypes = new StringBuilder[parameterNames.length]; } else { StringBuilder typeName = fParamTypes[paramIndex]; if (typeName != null) { return typeName.length() > 0 ? typeName : null; } } StringBuilder typeName = new StringBuilder(); fParamTypes[paramIndex] = typeName; fBuf = typeName; String paramName = parameterNames[paramIndex]; String info = getParameterInfo(fJavadoc, paramName, PARAMETER_TYPE_TYPE); if (info != null) typeName.append(info); fBuf = null; return typeName.length() > 0 ? typeName : null; } return null; } CharSequence getExceptionDescription(String simpleName) { if (fMethod != null) { cacheAllNewExceptionsAndDescriptions(); StringBuilder description = fExceptionDescriptions.get(simpleName); return description != null && description.length() > 0 ? description : null; } return null; } List<PHPDocTag> getExceptions() { if (fMethod != null) { cacheAllNewExceptionsAndDescriptions(); return fExceptions != null && fExceptions.size() > 0 ? fExceptions : null; } return null; } void cacheAllNewExceptionsAndDescriptions() { if (fExceptionDescriptions != null) { return; } fExceptionDescriptions = new HashMap<>(); fExceptions = new ArrayList<>(); List tags = Arrays.asList(fJavadoc.getTags(TagKind.THROWS)); for (Iterator iter = tags.iterator(); iter.hasNext();) { PHPDocTag tag = (PHPDocTag) iter.next(); fExceptions.add(tag); List<TypeReference> fragments = tag.getTypeReferences(); if (fragments.size() > 0) { String name = fragments.get(0).getName(); // keep the first found match if (!fExceptionDescriptions.containsKey(name)) { StringBuilder description = new StringBuilder(); fExceptionDescriptions.put(name, description); fBuf = description; handleContentElements(tag); fBuf = null; } } } } private void handleVarTag(PHPDocTag tag) { if (!tag.isValidVarTag()) { return; } fBuf.append(PARAM_NAME_START); fBuf.append(tag.getSingleTypeReference().getName()); fBuf.append(PARAM_NAME_END); fBuf.append(tag.getTrimmedDescText()); } private void handleContentElements(PHPDocTag tag) { fBuf.append(tag.getValue()); } private boolean handleInherited(CharSequence inherited) { if (inherited == null) return false; fBuf.append(inherited); return true; } private void handleBlockTags(String title, List tags) { if (tags.size() == 0) return; handleBlockTagTitle(title); for (Iterator iter = tags.iterator(); iter.hasNext();) { PHPDocTag tag = (PHPDocTag) iter.next(); fBuf.append(BlOCK_TAG_ENTRY_START); if (TagKind.SEE == tag.getTagKind()) { handleSeeTag(tag); } else { handleContentElements(tag); } doWorkAround(); fBuf.append(BlOCK_TAG_ENTRY_END); } } private void handleReturnTag(PHPDocTag tag) { String returnType = getReturnInfo(tag, RETURN_TYPE_TYPE); String description = getReturnInfo(tag, RETURN_DESCRIPTION_TYPE); if (returnType != null) { fBuf.append(PARAM_RETURN_START); fBuf.append(returnType); fBuf.append(PARAM_RETURN_END); } if (description != null) { fBuf.append(description); } } private void handleReturnTag(PHPDocTag tag, CharSequence returnDescription) { if (tag == null && returnDescription == null) return; handleBlockTagTitle(PHPDocumentationMessages.JavaDoc2HTMLTextReader_returns_section); fBuf.append(BlOCK_TAG_ENTRY_START); if (tag != null) { handleReturnTag(tag); } else { fBuf.append(returnDescription); } doWorkAround(); fBuf.append(BlOCK_TAG_ENTRY_END); } private void handleNamespaceTag(PHPDocTag tag) { if (tag == null) return; handleBlockTagTitle(PHPDocumentationMessages.JavaDoc2HTMLTextReader_namespace_section); fBuf.append(BlOCK_TAG_ENTRY_START); if (tag != null) { handleContentElements(tag); } doWorkAround(); fBuf.append(BlOCK_TAG_ENTRY_END); } private void handleBlockTags(List tags) { for (Iterator iter = tags.iterator(); iter.hasNext();) { PHPDocTag tag = (PHPDocTag) iter.next(); if (tag.getTagKind() == TagKind.INHERITDOC) { continue; } else if (tag.getTagKind() == TagKind.VAR) { if (!tag.isValidVarTag()) { continue; } // https://bugs.eclipse.org/bugs/show_bug.cgi?id=454140 // only print @var tags having empty variable name or // having variable name matching the field description if (fMethod == null && tag.getVariableReference() != null && !(tag.getVariableReference().getName().equals(fMember.getElementName()) || // also handle const fields (never prefixed by // '$') tag.getVariableReference().getName().equals('$' + fMember.getElementName()))) { continue; } handleBlockTagTitle(PHPDocumentationMessages.JavaDoc2HTMLTextReader_var_section); } else { handleBlockTagTitle(tag.getTagKind().getName()); } fBuf.append(BlOCK_TAG_ENTRY_START); if (tag.getTagKind() == TagKind.LINK) { handleLinkTag(tag); } else if (tag.getTagKind() == TagKind.VAR) { handleVarTag(tag); } else { handleContentElements(tag); } doWorkAround(); fBuf.append(BlOCK_TAG_ENTRY_END); } } private void handleBlockTagTitle(String title) { fBuf.append("<dt>"); //$NON-NLS-1$ fBuf.append(title); fBuf.append("</dt>"); //$NON-NLS-1$ } private void handleExceptionTags(List tags, List exceptionNames, CharSequence[] exceptionDescriptions) { if (tags.size() == 0 && containsOnlyNull(exceptionNames)) return; handleBlockTagTitle(PHPDocumentationMessages.JavaDoc2HTMLTextReader_throws_section); ArrayList<String> descList = new ArrayList<>(); for (Iterator iter = tags.iterator(); iter.hasNext();) { PHPDocTag tag = (PHPDocTag) iter.next(); List<TypeReference> fragments = tag.getTypeReferences(); if (fragments.size() > 0) { descList.add(handleThrowsException(fragments.get(0).getName().trim(), tag.getValue())); } } for (int i = 0; i < exceptionDescriptions.length; i++) { // by construction, the value of "name" must be a tag name in the // "tags" list String name = (String) exceptionNames.get(i); CharSequence rawDescription = exceptionDescriptions[i]; if (name != null) { String fullDesc = handleThrowsException(name, rawDescription); String emptyDesc = handleThrowsException(name, null); // Add the inherited exception description when it differs from // the tag exception description. // Also only add the inherited description when it is a full // exception description. if (!descList.contains(fullDesc) && !fullDesc.equals(emptyDesc)) { boolean wasEmptyDescFound = false; // replace existing empty descriptions by full descriptions for (int j = 0; j < descList.size(); j++) { if (emptyDesc.equals(descList.get(j))) { descList.set(j, fullDesc); wasEmptyDescFound = true; } } // add the inherited exception description when necessary if (!wasEmptyDescFound) { descList.add(fullDesc); } } } } for (int i = 0; i < descList.size(); i++) { fBuf.append(BlOCK_TAG_ENTRY_START); fBuf.append(descList.get(i)); doWorkAround(); fBuf.append(BlOCK_TAG_ENTRY_END); } } private String handleThrowsException(String exceptionName, @Nullable CharSequence rawValue) { StringBuffer fBuf = new StringBuffer(); fBuf.append(PARAM_THROWS_START); fBuf.append(exceptionName); fBuf.append(PARAM_THROWS_END); if (rawValue == null) { return fBuf.toString(); } String description = rawValue.toString().trim(); if (description.startsWith(exceptionName)) { description = description.substring(exceptionName.length()); description = description.trim(); } if (description.length() > 0) { fBuf.append(ScriptElementLabels.CONCAT_STRING); fBuf.append(description); } return fBuf.toString(); } private void handleParameterTags(List tags, List parameterNames, CharSequence[] parameterTypes, CharSequence[] parameterDescriptions) { if (tags.size() == 0 && containsOnlyNull(parameterNames)) return; handleBlockTagTitle(PHPDocumentationMessages.JavaDoc2HTMLTextReader_parameters_section); for (Iterator iter = tags.iterator(); iter.hasNext();) { PHPDocTag tag = (PHPDocTag) iter.next(); handleParamTag(tag); } for (int i = 0; i < parameterDescriptions.length; i++) { CharSequence description = parameterDescriptions[i]; String name = (String) parameterNames.get(i); if (name != null) { fBuf.append(BlOCK_TAG_ENTRY_START); if (parameterTypes[i] != null) { fBuf.append(PARAM_NAME_START); fBuf.append(parameterTypes[i]); fBuf.append(PARAM_NAME_END); } fBuf.append(PARAM_NAME_END); fBuf.append(PARAM_NAME_START); fBuf.append(name); fBuf.append(PARAM_NAME_END); if (description != null) { fBuf.append(description); } doWorkAround(); fBuf.append(BlOCK_TAG_ENTRY_END); } } } private void handleParamTag(PHPDocTag tag) { if (tag.getTypeReferences().size() == 0) { fBuf.append(BlOCK_TAG_ENTRY_START); fBuf.append(tag.getValue()); doWorkAround(); fBuf.append(BlOCK_TAG_ENTRY_END); return; } String parameterName = getParameterInfo(tag, PARAMETER_NAME_TYPE); String parameterType = getParameterInfo(tag, PARAMETER_TYPE_TYPE); String description = getParameterInfo(tag, PARAMETER_DESCRIPTION_TYPE); fBuf.append(BlOCK_TAG_ENTRY_START); if (parameterType != null) { fBuf.append(PARAM_NAME_START); fBuf.append(parameterType); fBuf.append(PARAM_NAME_END); } fBuf.append(PARAM_NAME_START); fBuf.append(parameterName); fBuf.append(PARAM_NAME_END); if (description != null) { fBuf.append(description); } doWorkAround(); fBuf.append(BlOCK_TAG_ENTRY_END); } private void handleLinkTag(PHPDocTag tag) { String uri = tag.getValue().trim(); fBuf.append("<a href=\""); //$NON-NLS-1$ fBuf.append(uri); fBuf.append("\">").append(uri).append("</a>"); //$NON-NLS-1$ //$NON-NLS-2$ } private void handleSeeTag(PHPDocTag tag) { fBuf.append(handleLinks(tag.getTypeReferences())); } private StringBuilder handleLinks(List<? extends TypeReference> fragments) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < fragments.size(); i++) { TypeReference reference = fragments.get(i); sb.append(handleLink(reference)); if (i < (fragments.size() - 1)) { sb.append(", "); //$NON-NLS-1$ } } return sb; } private StringBuilder handleLink(TypeReference type) { StringBuilder sb = new StringBuilder(); String refTypeName = null; String refMemberName = null; String[] refMethodParamTypes = null; String name = type.getName(); int typeNameEnd = name.indexOf("::"); //$NON-NLS-1$ if (typeNameEnd == -1) { refTypeName = name; } else { refTypeName = name.substring(0, typeNameEnd); int argsListStart = name.indexOf('(', typeNameEnd); if (argsListStart == -1) { refMemberName = name.substring(typeNameEnd + "::".length()); //$NON-NLS-1$ } else { refMemberName = name.substring(typeNameEnd + "::".length(), argsListStart); //$NON-NLS-1$ int argsListEnd = name.indexOf(')', argsListStart); if (argsListEnd == -1) { refMethodParamTypes = new String[0]; } else { String argsList = name.substring(argsListStart + ")".length(), argsListEnd); //$NON-NLS-1$ List<String> args = new ArrayList<>(); StringTokenizer tokenizer = new StringTokenizer(argsList, ","); //$NON-NLS-1$ while (tokenizer.hasMoreElements()) { args.add(tokenizer.nextToken().trim()); } refMethodParamTypes = args.toArray(new String[0]); } } } if (refTypeName != null) { sb.append("<a href='"); //$NON-NLS-1$ try { String scheme = PHPElementLinks.PHPDOC_SCHEME; String uri = PHPElementLinks.createURI(scheme, fMember, refTypeName, refMemberName, refMethodParamTypes); sb.append(uri); } catch (URISyntaxException e) { PHPUiPlugin.log(e); } sb.append("'>"); //$NON-NLS-1$ sb.append(refTypeName); if (refMemberName != null) { if (refTypeName.length() > 0) { sb.append("::"); //$NON-NLS-1$ } sb.append(refMemberName); if (refMethodParamTypes != null) { sb.append('('); for (int i = 0; i < refMethodParamTypes.length; i++) { String pType = refMethodParamTypes[i]; sb.append(pType); if (i < refMethodParamTypes.length - 1) { sb.append(", "); //$NON-NLS-1$ } } sb.append(')'); } } sb.append("</a>"); //$NON-NLS-1$ } return sb; } private boolean containsOnlyNull(List parameterNames) { for (Iterator iter = parameterNames.iterator(); iter.hasNext();) { if (iter.next() != null) return false; } return true; } private String getParameterInfo(PHPDocBlock phpDoc, String paramName, int infoType) { for (PHPDocTag tag : phpDoc.getTags(TagKind.PARAM)) { String name = getParameterInfo(tag, PARAMETER_NAME_TYPE); if (name != null && name.equals(paramName)) { return getParameterInfo(tag, infoType); } continue; } return null; } private String getParameterInfo(PHPDocTag tag, int infoType) { if (!tag.isValidParamTag()) { return null; } TypeReference typeRef = tag.getSingleTypeReference(); VariableReference variableRef = tag.getVariableReference(); String value = tag.getValue(); if (infoType == PARAMETER_DESCRIPTION_TYPE) { int typeRefIndex = value.indexOf(typeRef.getName()); int variableRefIndex = value.indexOf(variableRef.getName()); int lastRefIndex = typeRefIndex > variableRefIndex ? typeRefIndex + typeRef.getName().length() : variableRefIndex + variableRef.getName().length(); return value.substring(lastRefIndex).trim(); } else if (infoType == PARAMETER_TYPE_TYPE) { return typeRef.getName(); } else if (infoType == PARAMETER_NAME_TYPE) { return variableRef.getName(); } return null; } private String getReturnInfo(PHPDocTag tag, int infoType) { TypeReference typeRef = tag.getSingleTypeReference(); if (infoType == RETURN_DESCRIPTION_TYPE) { String value = tag.getValue(); if (typeRef == null) { return value.trim(); } int typeRefIndex = value.indexOf(typeRef.getName()); int lastRefIndex = typeRefIndex + typeRef.getName().length(); return value.substring(lastRefIndex).trim(); } else if (infoType == RETURN_TYPE_TYPE && typeRef != null) { return typeRef.getName(); } return null; } private static boolean appendBuiltinDoc(IMember element, StringBuilder buf) { String builtinDoc = BuiltinDoc.getString(element.getElementName()); if (builtinDoc.length() > 0) { buf.append(builtinDoc); return true; } return false; } // Work around for Bug 320709 // PHPDoc tooltips are not sized according to their contents // https://bugs.eclipse.org/bugs/show_bug.cgi?id=320709 private void doWorkAround() { fBuf.append(" "); //$NON-NLS-1$ } }