/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html * * 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.google.dart.engine.services.util; import com.google.dart.engine.AnalysisEngine; import com.google.dart.engine.context.AnalysisException; import com.google.dart.engine.element.ClassElement; import com.google.dart.engine.element.ConstructorElement; import com.google.dart.engine.element.Element; import com.google.dart.engine.element.ExecutableElement; import com.google.dart.engine.element.FieldElement; import com.google.dart.engine.element.FieldFormalParameterElement; import com.google.dart.engine.element.FunctionElement; import com.google.dart.engine.element.FunctionTypeAliasElement; import com.google.dart.engine.element.LibraryElement; import com.google.dart.engine.element.LocalVariableElement; import com.google.dart.engine.element.MethodElement; import com.google.dart.engine.element.ParameterElement; import com.google.dart.engine.element.PropertyAccessorElement; import com.google.dart.engine.element.TopLevelVariableElement; import com.google.dart.engine.element.TypeParameterElement; import com.google.dart.engine.element.VariableElement; import com.google.dart.engine.element.visitor.SimpleElementVisitor; import com.google.dart.engine.type.Type; import com.google.dart.engine.utilities.dart.ParameterKind; import org.apache.commons.lang3.StringUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; /** * A utility class for dealing with Dart doc text. * * @coverage dart.tools.core.utilities */ public final class DartDocUtilities { private static class DocumentingVisitor extends SimpleElementVisitor<String> { private Element targetElement; private Type targetType; public DocumentingVisitor(Element targetElement, Type targetType) { this.targetElement = targetElement; this.targetType = targetType; } @Override public String visitClassElement(ClassElement element) { LibraryElement library = element.getLibrary(); if (library != null) { String libraryName = library.getDisplayName(); if (libraryName != null && libraryName.length() > 0) { return getTypeName(element) + " - " + libraryName; } } return getTypeName(element); } @Override public String visitConstructorElement(ConstructorElement element) { StringBuilder params = describeParams(element.getParameters()); String typeName = element.getType().getReturnType().getDisplayName(); String constructorName = element.getDisplayName(); if (constructorName != null && constructorName.length() != 0) { typeName = typeName + "." + constructorName; } return typeName + "(" + params + ")"; } @Override public String visitFieldElement(FieldElement element) { return getTypeName(element) + " " + element.getDisplayName(); } @Override public String visitFieldFormalParameterElement(FieldFormalParameterElement element) { return getTypeName(element) + " " + element.getDisplayName(); } @Override public String visitFunctionElement(FunctionElement element) { return getDescription(element); } @Override public String visitFunctionTypeAliasElement(FunctionTypeAliasElement element) { return getDescription(element); } @Override public String visitLocalVariableElement(LocalVariableElement element) { return getTypeName(element) + " " + element.getDisplayName(); } @Override public String visitMethodElement(MethodElement element) { return getDescription(element); } @Override public String visitParameterElement(ParameterElement element) { return getDescription(element); } @Override public String visitPropertyAccessorElement(PropertyAccessorElement element) { if (element.isGetter()) { String returnTypeName = getName(element.getType().getReturnType()); StringBuilder sb = new StringBuilder(); if (returnTypeName != null) { sb.append(returnTypeName).append(' '); } sb.append("get ").append(element.getDisplayName()); return sb.toString(); } if (element.isSetter()) { return element.getDisplayName() + "(" + describeParams(element.getParameters()) + ")"; } return getDescription(element); } @Override public String visitTopLevelVariableElement(TopLevelVariableElement element) { return getTypeName(element) + " " + element.getDisplayName(); } @Override public String visitTypeParameterElement(TypeParameterElement element) { StringBuilder sb = new StringBuilder(); sb.append("<"); sb.append(element.getType().getDisplayName()); com.google.dart.engine.type.Type bound = element.getBound(); if (bound != null) { sb.append(" extends ").append(bound.getDisplayName()); } sb.append(">"); return sb.toString(); } private StringBuilder describeParams(ParameterElement[] parameters) { StringBuilder buf = new StringBuilder(); // Closing ']' or '}' in case of optional params String endGroup = ""; for (int i = 0; i < parameters.length; i++) { if (i > 0) { buf.append(", "); } ParameterElement param = parameters[i]; ParameterKind kind = param.getParameterKind(); // Start group of optional params if (endGroup.length() == 0) { if (kind == ParameterKind.NAMED) { buf.append("{"); endGroup = "}"; } else if (kind == ParameterKind.POSITIONAL) { buf.append("["); endGroup = "]"; } } buf.append(getDescription(param)); } // Close optional list buf.append(endGroup); return buf; } private String getDescription(ExecutableElement element) { StringBuilder params = describeParams(element.getParameters()); String returnTypeName = getName(element.getType().getReturnType()); if (returnTypeName != null) { return returnTypeName + " " + element.getDisplayName() + "(" + params + ")"; } else { return element.getDisplayName() + "(" + params + ")"; } } private String getDescription(FunctionTypeAliasElement element) { StringBuilder params = describeParams(element.getParameters()); String returnTypeName = getName(element.getType().getReturnType()); if (returnTypeName != null) { return returnTypeName + " " + element.getDisplayName() + "(" + params + ")"; } else { return element.getDisplayName() + "(" + params + ")"; } } private String getDescription(ParameterElement param) { StringBuilder buf = new StringBuilder(); String typeName = getTypeName(param); String paramName = param.getDisplayName(); if (typeName.indexOf('(') != -1) { // Instead of returning "void(var) callback", return "void callback(var)". int index = typeName.indexOf('('); buf.append(typeName.substring(0, index)); buf.append(" "); buf.append(paramName); buf.append(typeName.substring(index)); } else { buf.append(typeName + " " + paramName); } String defaultValueCode = param.getDefaultValueCode(); if (defaultValueCode != null) { buf.append(": "); buf.append(defaultValueCode); } return buf.toString(); } private String getName(com.google.dart.engine.type.Type type) { if (type != null) { String name = type.getDisplayName(); if (name != null) { return name; } } return "dynamic"; } private String getTypeName(ClassElement element) { return getName(element.getType()); } private String getTypeName(VariableElement element) { Type type = element.getType(); if (targetType != null && targetElement != null && targetElement.equals(element)) { type = targetType; } return getName(type); } } /** * Convert from a Dart doc string with slashes and stars to a plain text representation of the * comment. * * @param str * @return */ public static String cleanDartDoc(String str) { // Remove /** */ if (str.startsWith("/**")) { str = str.substring(3); } if (str.endsWith("*/")) { str = str.substring(0, str.length() - 2); } str = str.trim(); // Remove leading '* ', and turn empty lines into \n's. StringBuilder builder = new StringBuilder(); BufferedReader reader = new BufferedReader(new StringReader(str)); try { String line = reader.readLine(); int lineCount = 0; while (line != null) { line = line.trim(); if (line.startsWith("*")) { line = line.substring(1); if (line.startsWith(" ")) { line = line.substring(1); } } else if (line.startsWith("///")) { line = line.substring(3); if (line.startsWith(" ")) { line = line.substring(1); } } if (line.length() == 0) { lineCount = 0; builder.append("\n\n"); } else { if (lineCount > 0) { builder.append("\n"); } builder.append(line); lineCount++; } line = reader.readLine(); } } catch (IOException exception) { // this will never be thrown } return builder.toString(); } /** * Return the prettified DartDoc text for the given element. */ public static String getDartDocAsHtml(Element element) { if (element == null) { return null; } try { String docString = element.computeDocumentationComment(); return getDartDocAsHtml(docString); } catch (AnalysisException e) { AnalysisEngine.getInstance().getLogger().logError("Exception in gettting documentation", e); } return null; } /** * Return the prettified DartDoc text for the given element. */ public static String getDartDocAsHtml(String docString) { if (docString != null) { return convertToHtml(cleanDartDoc(docString)); } return null; } /** * Return the prettified DartDoc text for the given element. */ public static String getDartDocAsHtml2(String docString) { if (docString != null) { return convertToHtml(docString); } return null; } /** * Return a one-line description of the given Element. * * @param type the best known {@link Type} of the given {@link Element} * @param element the {@link Element} to document * @return a String summarizing this element, or {@code null} if there is no suitable * documentation */ public static String getTextSummary(Type type, Element element) { if (element != null) { String description = element.accept(new DocumentingVisitor(element, type)); if (description != null) { return description; } } return null; } /** * Return a one-line description of the given Element as html */ public static String getTextSummaryAsHtml(Type type, Element element) { String summary = getTextSummary(type, element); if (summary == null) { return null; } return escapeHtmlEntities(summary); } private static String convertListItems(String[] lines) { StringBuffer buf = new StringBuffer(); boolean wasCode = false; for (String line : lines) { // code block if (line.startsWith(" ")) { buf.append("<pre>"); if (wasCode) { buf.append("\n"); } buf.append(line); buf.append("</pre>"); wasCode = true; continue; } wasCode = false; // empty line if (line.isEmpty()) { buf.append("\n\n"); continue; } // unordered list item if (line.startsWith("* ")) { buf.append("<li>" + line.substring(2) + "</li>\n"); continue; } // ordered list { line = line.trim(); int i = 0; while (i < line.length() && Character.isDigit(line.charAt(i))) { i++; } if (i < line.length() && line.charAt(i) == '.') { buf.append("<pre> </pre>" + line + "<br>"); continue; } } // text line buf.append(line); buf.append("\n"); } return buf.toString(); } private static String convertToHtml(String str) { if (str == null) { return null; } // escape html entities str = escapeHtmlEntities(str); // convert lines starting with " " to list items str = convertListItems(str.split("\n")); // insert line breaks where appropriate str = str.replace("\n\n", "\n<br><br>\n"); // handle too much whitespace in front of list items str = str.replace("<br>\n<li>", "<li>"); // process non-code lines { String[] lines = StringUtils.splitPreserveAllTokens(str, "\n"); StringBuilder sb = new StringBuilder(); for (String line : lines) { if (line.startsWith(" ") || line.startsWith("<pre> ")) { // ignore code lines } else { // convert code and reference blocks line = line.replace("[:", "<code>").replace(":]", "</code>"); line = line.replace("[", "<code>").replace("]", "</code>"); line = replacePairs(line, "`", "<code>", "</code>"); // convert bold and italic line = replacePairs(line, "**", "<b>", "</b>"); line = replacePairs(line, "__", "<b>", "</b>"); line = replacePairs(line, "*", "<i>", "</i>"); line = replacePairs(line, "_", "<i>", "</i>"); } sb.append(line); sb.append("\n"); } str = sb.toString(); if (str.length() > 0) { str = str.substring(0, str.length() - 1); } } return str; } private static String escapeHtmlEntities(String str) { return str.replace("&", "&").replace("<", "<").replace(">", ">"); } private static String replacePairs(String string, String delimiter, String leftReplacement, String rightReplacement) { int index = string.indexOf(delimiter); if (index < 0) { return string; } StringBuilder builder = new StringBuilder(); boolean left = true; int start = 0; int length = delimiter.length(); while (index >= 0) { builder.append(string, start, index); builder.append(left ? leftReplacement : rightReplacement); start = index + length; left = !left; index = string.indexOf(delimiter, start); } builder.append(string, start, string.length()); return builder.toString(); } private DartDocUtilities() { } }