Commit 2cab2ce1 authored by Evan W. Patton's avatar Evan W. Patton Committed by Jeffrey Schiller

Use Javadocs for reference documentation descriptions

Change-Id: Icdd373c2090864129a3fba150529a035bd3519d3
parent 050ea9a3
...@@ -37,6 +37,10 @@ ...@@ -37,6 +37,10 @@
<ant inheritAll="false" useNativeBasedir="true" dir="buildserver" target="BuildServer"/> <ant inheritAll="false" useNativeBasedir="true" dir="buildserver" target="BuildServer"/>
</target> </target>
<target name="docs">
<ant inheritAll="false" useNativeBasedir="true" dir="appengine" target="CopyToBuildWar"/>
</target>
<target name="extensions"> <target name="extensions">
<ant inheritAll="false" useNativeBasedir="true" dir="components" target="clean"/> <ant inheritAll="false" useNativeBasedir="true" dir="components" target="clean"/>
<ant inheritAll="false" useNativeBasedir="true" dir="components" target="extensions"/> <ant inheritAll="false" useNativeBasedir="true" dir="components" target="extensions"/>
......
...@@ -73,6 +73,8 @@ import javax.lang.model.util.Types; ...@@ -73,6 +73,8 @@ import javax.lang.model.util.Types;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.tools.Diagnostic; import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
...@@ -257,19 +259,145 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -257,19 +259,145 @@ public abstract class ComponentProcessor extends AbstractProcessor {
* Represents a component feature that has a name and a description. * Represents a component feature that has a name and a description.
*/ */
protected abstract static class Feature { protected abstract static class Feature {
private static final Pattern AT_SIGN = Pattern.compile("[^\\\\]@");
private static final Pattern LINK_FORM = Pattern.compile("\\{@link ([A-Za-z]*#?)([A-Za-z]*)[^}]*}");
private static final Pattern CODE_FORM = Pattern.compile("\\{@code ([^}]*)}");
private final String featureType;
protected final String name; protected final String name;
protected String description; protected String description;
protected boolean defaultDescription = false;
protected String longDescription;
protected boolean userVisible;
protected boolean deprecated;
protected Feature(String name, String description, String featureType) { protected Feature(String name, String description, String longDescription, String featureType,
boolean userVisible, boolean deprecated) {
this.featureType = featureType;
this.name = name; this.name = name;
setDescription(description);
setLongDescription(longDescription);
this.userVisible = userVisible;
this.deprecated = deprecated;
}
public boolean isDefaultDescription() {
return defaultDescription;
}
public void setDescription(String description) {
if (description == null || description.isEmpty()) { if (description == null || description.isEmpty()) {
this.description = featureType + " for " + name; this.description = featureType + " for " + name;
defaultDescription = true;
} else { } else {
// Throw out the first @ or { and everything after it, // Throw out the first @ or { and everything after it,
// in order to strip out @param, @author, {@link ...}, etc. // in order to strip out @param, @author, {@link ...}, etc.
this.description = description.split("[@{]")[0].trim(); this.description = description.split("[@{]")[0].trim();
defaultDescription = false;
} }
} }
public void setLongDescription(String longDescription) {
if (longDescription == null || longDescription.isEmpty()) {
this.longDescription = this.description;
} else if (longDescription.contains("@suppressdoc")) {
this.longDescription = "";
} else {
this.longDescription = longDescription;
}
// Handle links
Matcher linkMatcher = LINK_FORM.matcher(this.longDescription);
StringBuffer sb = new StringBuffer();
int lastEnd = 0;
while (linkMatcher.find(lastEnd)) {
sb.append(this.longDescription, lastEnd, linkMatcher.start());
String clazz = linkMatcher.group(1);
if (clazz.endsWith("#")) {
clazz = clazz.substring(0, clazz.length() - 1);
}
if ("Form".equals(clazz)) {
clazz = "Screen";
}
String func = linkMatcher.group(2);
sb.append("[");
if (!clazz.isEmpty()) {
sb.append("`");
sb.append(clazz);
sb.append("`");
if (!func.isEmpty()) {
sb.append("'s ");
}
}
if (!func.isEmpty()) {
sb.append("`");
sb.append(func);
sb.append("`");
}
sb.append("](#");
if (clazz.isEmpty()) {
sb.append("%type%.");
} else {
sb.append(clazz);
if (!func.isEmpty()) {
sb.append(".");
}
}
if (!func.isEmpty()) {
sb.append(func);
}
sb.append(")");
lastEnd = linkMatcher.end();
}
sb.append(this.longDescription.substring(lastEnd));
this.longDescription = sb.toString();
// Map {@code foo} to `foo`
sb = new StringBuffer();
Matcher codeMatcher = CODE_FORM.matcher(this.longDescription);
lastEnd = 0;
while (codeMatcher.find(lastEnd)) {
sb.append(this.longDescription, lastEnd, codeMatcher.start());
sb.append("`");
sb.append(codeMatcher.group(1));
sb.append("`");
lastEnd = codeMatcher.end();
}
sb.append(this.longDescription.substring(lastEnd));
this.longDescription = sb.toString();
// Strip out the Javadoc annotations (@param, etc.) for end-user documentation
Matcher m = AT_SIGN.matcher(this.longDescription);
if (m.find()) {
this.longDescription = this.longDescription.substring(0, m.start() + 1);
}
// Replace escaped @ with just @, e.g., so we can use @ in email address examples.
this.longDescription = this.longDescription.replaceAll("\\\\@", "@").trim();
}
public String getLongDescription(ComponentInfo component) {
if (longDescription == null || longDescription.isEmpty()) {
return description;
}
String name = component.name.equals("Form") ? "Screen" : component.name;
return longDescription.replaceAll("%type%", name).trim();
}
/**
* Returns whether this property is visible in the Blocks Editor, as retrieved
* from {@link SimpleProperty#userVisible()}.
*
* @return whether the property is visible in the Blocks Editor
*/
protected boolean isUserVisible() {
return userVisible;
}
/**
* Returns whether this property is deprecated in the Blocks Editor.
*
* @return whether the property is visible in the Blocks Editor
*/
protected boolean isDeprecated() {
return deprecated;
}
} }
/** /**
...@@ -279,14 +407,10 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -279,14 +407,10 @@ public abstract class ComponentProcessor extends AbstractProcessor {
protected abstract class ParameterizedFeature extends Feature { protected abstract class ParameterizedFeature extends Feature {
// Inherits name, description // Inherits name, description
protected final List<Parameter> parameters; protected final List<Parameter> parameters;
protected final boolean userVisible;
protected final boolean deprecated; // [lyn, 2015/12/29] added
protected ParameterizedFeature(String name, String description, String feature, protected ParameterizedFeature(String name, String description, String longDescription,
boolean userVisible, boolean deprecated) { String feature, boolean userVisible, boolean deprecated) {
super(name, description, feature); super(name, description, longDescription, feature, userVisible, deprecated);
this.userVisible = userVisible;
this.deprecated = deprecated;
parameters = Lists.newArrayList(); parameters = Lists.newArrayList();
} }
...@@ -328,13 +452,13 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -328,13 +452,13 @@ public abstract class ComponentProcessor extends AbstractProcessor {
implements Cloneable, Comparable<Event> { implements Cloneable, Comparable<Event> {
// Inherits name, description, and parameters // Inherits name, description, and parameters
protected Event(String name, String description, boolean userVisible, boolean deprecated) { protected Event(String name, String description, String longDescription, boolean userVisible, boolean deprecated) {
super(name, description, "Event", userVisible, deprecated); super(name, description, longDescription, "Event", userVisible, deprecated);
} }
@Override @Override
public Event clone() { public Event clone() {
Event that = new Event(name, description, userVisible, deprecated); Event that = new Event(name, description, longDescription, userVisible, deprecated);
for (Parameter p : parameters) { for (Parameter p : parameters) {
that.addParameter(p.name, p.type); that.addParameter(p.name, p.type);
} }
...@@ -357,8 +481,9 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -357,8 +481,9 @@ public abstract class ComponentProcessor extends AbstractProcessor {
private String returnType; private String returnType;
private boolean color; private boolean color;
protected Method(String name, String description, boolean userVisible, boolean deprecated) { protected Method(String name, String description, String longDescription, boolean userVisible,
super(name, description, "Method", userVisible, deprecated); boolean deprecated) {
super(name, description, longDescription, "Method", userVisible, deprecated);
// returnType defaults to null // returnType defaults to null
} }
...@@ -372,7 +497,7 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -372,7 +497,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
@Override @Override
public Method clone() { public Method clone() {
Method that = new Method(name, description, userVisible, deprecated); Method that = new Method(name, description, longDescription, userVisible, deprecated);
for (Parameter p : parameters) { for (Parameter p : parameters) {
that.addParameter(p.name, p.type); that.addParameter(p.name, p.type);
} }
...@@ -390,32 +515,28 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -390,32 +515,28 @@ public abstract class ComponentProcessor extends AbstractProcessor {
* Represents an App Inventor component property (annotated with * Represents an App Inventor component property (annotated with
* {@link SimpleProperty}). * {@link SimpleProperty}).
*/ */
protected static final class Property implements Cloneable { protected static final class Property extends Feature implements Cloneable {
protected final String name; protected final String name;
private String description;
private PropertyCategory propertyCategory; private PropertyCategory propertyCategory;
private boolean userVisible;
private boolean deprecated;
private String type; private String type;
private boolean readable; private boolean readable;
private boolean writable; private boolean writable;
private String componentInfoName; private String componentInfoName;
private boolean color; private boolean color;
protected Property(String name, String description, protected Property(String name, String description, String longDescription,
PropertyCategory category, boolean userVisible, boolean deprecated) { PropertyCategory category, boolean userVisible, boolean deprecated) {
super(name, description, longDescription, "Property", userVisible, deprecated);
this.name = name; this.name = name;
this.description = description;
this.propertyCategory = category; this.propertyCategory = category;
this.userVisible = userVisible;
this.deprecated = deprecated;
// type defaults to null // type defaults to null
// readable and writable default to false // readable and writable default to false
} }
@Override @Override
public Property clone() { public Property clone() {
Property that = new Property(name, description, propertyCategory, userVisible, deprecated); Property that = new Property(name, description, longDescription, propertyCategory,
isUserVisible(), isDeprecated());
that.type = type; that.type = type;
that.readable = readable; that.readable = readable;
that.writable = writable; that.writable = writable;
...@@ -450,25 +571,6 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -450,25 +571,6 @@ public abstract class ComponentProcessor extends AbstractProcessor {
return description; return description;
} }
/**
* Returns whether this property is visible in the Blocks Editor, as retrieved
* from {@link SimpleProperty#userVisible()}.
*
* @return whether the property is visible in the Blocks Editor
*/
protected boolean isUserVisible() {
return userVisible;
}
/**
* Returns whether this property is deprecated in the Blocks Editor.
*
* @return whether the property is visible in the Blocks Editor
*/
protected boolean isDeprecated() {
return deprecated;
}
/** /**
* Returns this property's Java type (e.g., "int", "double", or "java.lang.String"). * Returns this property's Java type (e.g., "int", "double", or "java.lang.String").
* *
...@@ -640,7 +742,8 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -640,7 +742,8 @@ public abstract class ComponentProcessor extends AbstractProcessor {
protected ComponentInfo(Element element) { protected ComponentInfo(Element element) {
super(element.getSimpleName().toString(), // Short name super(element.getSimpleName().toString(), // Short name
elementUtils.getDocComment(element), elementUtils.getDocComment(element),
"Component"); elementUtils.getDocComment(element),
"Component", false, elementUtils.isDeprecated(element));
type = element.asType().toString(); type = element.asType().toString();
displayName = getDisplayNameForComponentType(name); displayName = getDisplayNameForComponentType(name);
permissions = Sets.newHashSet(); permissions = Sets.newHashSet();
...@@ -698,7 +801,7 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -698,7 +801,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
// if provided. // if provided.
String explicitDescription = designerComponentAnnotation.description(); String explicitDescription = designerComponentAnnotation.description();
if (!explicitDescription.isEmpty()) { if (!explicitDescription.isEmpty()) {
description = explicitDescription; setDescription(explicitDescription);
} }
// Set helpDescription to the designerHelpDescription field if // Set helpDescription to the designerHelpDescription field if
...@@ -720,6 +823,7 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -720,6 +823,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
iconName = designerComponentAnnotation.iconName(); iconName = designerComponentAnnotation.iconName();
androidMinSdk = designerComponentAnnotation.androidMinSdk(); androidMinSdk = designerComponentAnnotation.androidMinSdk();
versionName = designerComponentAnnotation.versionName(); versionName = designerComponentAnnotation.versionName();
userVisible = designerComponentAnnotation.showOnPalette();
} }
} }
} }
...@@ -1172,6 +1276,7 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -1172,6 +1276,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
// Use Javadoc for property unless description is set to a non-empty string. // Use Javadoc for property unless description is set to a non-empty string.
String description = elementUtils.getDocComment(element); String description = elementUtils.getDocComment(element);
String longDescription = description;
if (!simpleProperty.description().isEmpty()) { if (!simpleProperty.description().isEmpty()) {
description = simpleProperty.description(); description = simpleProperty.description();
} }
...@@ -1183,6 +1288,7 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -1183,6 +1288,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
Property property = new Property(propertyName, Property property = new Property(propertyName,
description, description,
longDescription,
simpleProperty.category(), simpleProperty.category(),
simpleProperty.userVisible(), simpleProperty.userVisible(),
elementUtils.isDeprecated(element)); elementUtils.isDeprecated(element));
...@@ -1458,9 +1564,14 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -1458,9 +1564,14 @@ public abstract class ComponentProcessor extends AbstractProcessor {
} }
// Merge newProperty into priorProperty, which is already in the properties map. // Merge newProperty into priorProperty, which is already in the properties map.
if ((priorProperty.description.isEmpty() || element.getAnnotation(Override.class) != null) if ((priorProperty.description.isEmpty() || priorProperty.isDefaultDescription()
&& !newProperty.description.isEmpty()) { || element.getAnnotation(Override.class) != null)
priorProperty.description = newProperty.description; && !newProperty.description.isEmpty() && !newProperty.isDefaultDescription()) {
priorProperty.setDescription(newProperty.description);
}
if (!newProperty.longDescription.isEmpty()) { /* Latter descriptions of the same property
override earlier descriptions. */
priorProperty.longDescription = newProperty.longDescription;
} }
if (priorProperty.propertyCategory == PropertyCategory.UNSET) { if (priorProperty.propertyCategory == PropertyCategory.UNSET) {
priorProperty.propertyCategory = newProperty.propertyCategory; priorProperty.propertyCategory = newProperty.propertyCategory;
...@@ -1474,8 +1585,8 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -1474,8 +1585,8 @@ public abstract class ComponentProcessor extends AbstractProcessor {
} }
priorProperty.readable = priorProperty.readable || newProperty.readable; priorProperty.readable = priorProperty.readable || newProperty.readable;
priorProperty.writable = priorProperty.writable || newProperty.writable; priorProperty.writable = priorProperty.writable || newProperty.writable;
priorProperty.userVisible = priorProperty.userVisible && newProperty.userVisible; priorProperty.userVisible = priorProperty.isUserVisible() && newProperty.isUserVisible();
priorProperty.deprecated = priorProperty.deprecated && newProperty.deprecated; priorProperty.deprecated = priorProperty.isDeprecated() && newProperty.isDeprecated();
priorProperty.componentInfoName = componentInfo.name; priorProperty.componentInfoName = componentInfo.name;
priorProperty.color = newProperty.color || priorProperty.color; priorProperty.color = newProperty.color || priorProperty.color;
} else { } else {
...@@ -1524,8 +1635,9 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -1524,8 +1635,9 @@ public abstract class ComponentProcessor extends AbstractProcessor {
} }
} else { } else {
String eventDescription = simpleEventAnnotation.description(); String eventDescription = simpleEventAnnotation.description();
String longEventDescription = elementUtils.getDocComment(element);
if (eventDescription.isEmpty()) { if (eventDescription.isEmpty()) {
eventDescription = elementUtils.getDocComment(element); eventDescription = longEventDescription;
if (eventDescription == null) { if (eventDescription == null) {
messager.printMessage(Diagnostic.Kind.WARNING, messager.printMessage(Diagnostic.Kind.WARNING,
"In component " + componentInfo.name + "In component " + componentInfo.name +
...@@ -1536,7 +1648,7 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -1536,7 +1648,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
} }
boolean userVisible = simpleEventAnnotation.userVisible(); boolean userVisible = simpleEventAnnotation.userVisible();
boolean deprecated = elementUtils.isDeprecated(element); boolean deprecated = elementUtils.isDeprecated(element);
Event event = new Event(eventName, eventDescription, userVisible, deprecated); Event event = new Event(eventName, eventDescription, longEventDescription, userVisible, deprecated);
componentInfo.events.put(event.name, event); componentInfo.events.put(event.name, event);
// Verify that this element has an ExecutableType. // Verify that this element has an ExecutableType.
...@@ -1578,9 +1690,10 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -1578,9 +1690,10 @@ public abstract class ComponentProcessor extends AbstractProcessor {
componentInfo.methods.remove(methodName); componentInfo.methods.remove(methodName);
} }
} else { } else {
String methodLongDescription = elementUtils.getDocComment(element);
String methodDescription = simpleFunctionAnnotation.description(); String methodDescription = simpleFunctionAnnotation.description();
if (methodDescription.isEmpty()) { if (methodDescription.isEmpty()) {
methodDescription = elementUtils.getDocComment(element); methodDescription = methodLongDescription;
if (methodDescription == null) { if (methodDescription == null) {
messager.printMessage(Diagnostic.Kind.WARNING, messager.printMessage(Diagnostic.Kind.WARNING,
"In component " + componentInfo.name + "In component " + componentInfo.name +
...@@ -1591,7 +1704,7 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -1591,7 +1704,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
} }
boolean userVisible = simpleFunctionAnnotation.userVisible(); boolean userVisible = simpleFunctionAnnotation.userVisible();
boolean deprecated = elementUtils.isDeprecated(element); boolean deprecated = elementUtils.isDeprecated(element);
Method method = new Method(methodName, methodDescription, userVisible, deprecated); Method method = new Method(methodName, methodDescription, methodLongDescription, userVisible, deprecated);
componentInfo.methods.put(method.name, method); componentInfo.methods.put(method.name, method);
// Verify that this element has an ExecutableType. // Verify that this element has an ExecutableType.
......
...@@ -30,7 +30,6 @@ public class MarkdownDocumentationGenerator extends ComponentProcessor { ...@@ -30,7 +30,6 @@ public class MarkdownDocumentationGenerator extends ComponentProcessor {
categoryDocs.put(component.getCategoryString(), new StringBuilder()); categoryDocs.put(component.getCategoryString(), new StringBuilder());
} }
if (!sortedComponents.containsKey(component.getCategoryString())) { if (!sortedComponents.containsKey(component.getCategoryString())) {
//noinspection Convert2Diamond
sortedComponents.put(component.getCategoryString(), new TreeMap<String, ComponentInfo>()); sortedComponents.put(component.getCategoryString(), new TreeMap<String, ComponentInfo>());
} }
categoryNames.put(component.getCategoryString(), component.getCategory()); categoryNames.put(component.getCategoryString(), component.getCategory());
...@@ -59,34 +58,45 @@ public class MarkdownDocumentationGenerator extends ComponentProcessor { ...@@ -59,34 +58,45 @@ public class MarkdownDocumentationGenerator extends ComponentProcessor {
String name = entry.getKey(); String name = entry.getKey();
out.write("\n\n## "); out.write("\n\n## ");
out.write(name); out.write(name);
out.write(" {#" + name + "}"); out.write(" {#" + name + "}\n\n");
ComponentInfo info = entry.getValue(); ComponentInfo info = entry.getValue();
out.write(info.getLongDescription(info));
out.write("\n\n");
outputProperties(name, info, out); outputProperties(name, info, out);
outputEvents(name, info, out); outputEvents(name, info, out);
outputMethods(name, info, out); outputMethods(name, info, out);
} }
out.write("\n");
} }
} }
} }
private void outputProperties(String name, ComponentInfo info, Writer out) throws IOException { private void outputProperties(String name, ComponentInfo info, Writer out) throws IOException {
if (info.properties.size() == 0) {
return;
}
out.write(String.format("%n%n### Properties {#%s-Properties}%n%n{:.properties}", name)); out.write(String.format("%n%n### Properties {#%s-Properties}%n%n{:.properties}", name));
boolean didOutput = false;
for (Property property : info.properties.values()) { for (Property property : info.properties.values()) {
if (propertyIsHidden(info, property)) { if (isPropertyHidden(info, property)) {
continue; continue;
} }
out.write(String.format("%n%n{:id=\"%s.%s\" %s} *%s*%n: ", name, property.name, out.write(String.format("%n%n{:id=\"%s.%s\" %s} *%s*%n: ", name, property.name,
getPropertyClass(info, property), property.name)); getPropertyClass(info, property), property.name));
out.write(property.getDescription()); out.write(property.getLongDescription(info));
didOutput = true;
}
if (!didOutput) {
out.write("\nNone\n");
} }
} }
private boolean propertyIsHidden(ComponentInfo info, Property property) { private boolean isPropertyHidden(ComponentInfo info, Property property) {
return (!property.isUserVisible() && !info.designerProperties.containsKey(property.name)) return (isFeatureHidden(info, property) && !info.designerProperties.containsKey(property.name))
|| (info.displayName.equals("Screen") && property.name.equals("ActionBar")); || (info.displayName.equals("Screen") && property.name.equals("ActionBar"))
|| (info.displayName.equals("FirebaseDB") && property.name.equals("DefaultURL"))
|| (info.displayName.equals("CloudDB") && property.name.equals("DefaultRedisServer"));
}
private boolean isFeatureHidden(ComponentInfo info, Feature feature) {
return !feature.isUserVisible() || feature.isDeprecated();
} }
private String getPropertyClass(ComponentInfo info, Property property) { private String getPropertyClass(ComponentInfo info, Property property) {
...@@ -107,27 +117,27 @@ public class MarkdownDocumentationGenerator extends ComponentProcessor { ...@@ -107,27 +117,27 @@ public class MarkdownDocumentationGenerator extends ComponentProcessor {
} }
private void outputEvents(String name, ComponentInfo info, Writer out) throws IOException { private void outputEvents(String name, ComponentInfo info, Writer out) throws IOException {
if (info.events.size() == 0) {
return;
}
out.write(String.format("%n%n### Events {#%s-Events}%n%n{:.events}", name)); out.write(String.format("%n%n### Events {#%s-Events}%n%n{:.events}", name));
boolean didOutput = false;
for (Event event : info.events.values()) { for (Event event : info.events.values()) {
if (!event.userVisible) { if (isFeatureHidden(info, event)) {
continue; continue;
} }
out.write(String.format("%n%n{:id=\"%s.%s\"} %s(%s)%n: ", name, event.name, event.name, out.write(String.format("%n%n{:id=\"%s.%s\"} %s(%s)%n: ", name, event.name, event.name,
formatParameters(event.parameters))); formatParameters(event.parameters)));
out.write(event.description); out.write(event.getLongDescription(info));
didOutput = true;
}
if (!didOutput) {
out.write("\nNone\n");
} }
} }
private void outputMethods(String name, ComponentInfo info, Writer out) throws IOException { private void outputMethods(String name, ComponentInfo info, Writer out) throws IOException {
if (info.methods.size() == 0) {
return;
}
out.write(String.format("%n%n### Methods {#%s-Methods}%n%n{:.methods}", name)); out.write(String.format("%n%n### Methods {#%s-Methods}%n%n{:.methods}", name));
boolean didOutput = false;
for (Method method : info.methods.values()) { for (Method method : info.methods.values()) {
if (!method.userVisible) { if (isFeatureHidden(info, method)) {
continue; continue;
} }
String returnType = ""; String returnType = "";
...@@ -137,7 +147,11 @@ public class MarkdownDocumentationGenerator extends ComponentProcessor { ...@@ -137,7 +147,11 @@ public class MarkdownDocumentationGenerator extends ComponentProcessor {
} }
out.write(String.format("%n%n{:id=\"%s.%s\" class=\"method%s\"} <i/> %s(%s)%n: ", name, out.write(String.format("%n%n{:id=\"%s.%s\" class=\"method%s\"} <i/> %s(%s)%n: ", name,
method.name, returnType, method.name, formatParameters(method.parameters))); method.name, returnType, method.name, formatParameters(method.parameters)));
out.write(method.description); out.write(method.getLongDescription(info));
didOutput = true;
}
if (!didOutput) {
out.write("\nNone\n");
} }
} }
......
# Component Documentation in MIT App Inventor
## Reference Documentation
Long descriptions for the reference documentation are provided via Javadoc style comments. These comments can use Markdown formatting, `{@code ...}`, and `{@link #...}` to annotate content. Markdown formatting will be passed through to Jekyll for rendering when the documentation is built.
**Rule 1.** An empty Javadoc is ignored.
**Rule 2.** `{@code ...}` annotations in the Javadoc will be translated into Markdown inline code in single backticks.
**Rule 3.** `{@link #...}` can only be used to link within a given component. If you need to link across components, use the Markdown link format `[text]([category page]#Component.Block)`, for example `[Click](#Button.Click)` if linking to the Button Click event from within another User Interface component or `[Click](userinterface.html#Button.Click)` if linking from a component in another category.
**Rule 4.** The Javadoc content is only evaluated up to the first `@` character not escaped by a `\`.
**Rule 5.** If you want to include multiple paragraphs in the comment for a Property, Method, or Event, you will need to indent all but the first paragraph with 2 spaces, for example:
```java
/**
* This method does a thing.
*
* Note: Something important happens when this occurs,
* but it's mostly an implementation detail that you might
* be interested in.
*
* Learn more [here](http://example.com).
*/
```
**Rule 6.** If you want to suppress a Javadoc entirely from the user-facing documentation, include `@suppressdoc` anywhere in the Javadoc string.
**Rule 7.** If you want to suppress only a portion of the Javadoc, you can specify `@internaldoc` at the beginning of the non-public content. Only the content from the beginning of the comment to the `@internaldoc` will be put into the Markdown documentation.
## Tooltips
Tooltips for blocks are provided via the `description` field on the `@Simple*` annotations. If you have both getter and setters for a property in a component, you must put the `description` on the first element as it appears.
Tooltips are populated using the following algorithm:
1. When an annotated element is first encountered, the `description` field is first populated with the Javadoc as described in the previous section.
2. If the annotation specifies a description, it will overwrite the Javadoc version.
*NB: This only happens with the Javadoc and annotation on the same element. If you specify multiple annotationed elements (e.g., property getter and setter), this rule is only evaluated one for the property name. This can be combined with Tip 1 above.*
3. If both the Javadoc and description are empty, then a default description will be generated in the form of the string "%1 for %2" where %1 is the element type (e.g., "Property") and %2 is the element name (e.g., "Enabled").
4. If another element of the same name is annotated and contains a non-empty description, replace the existing description with the new description if either of these conditions is true:
1. The previously encountered description is empty, or
2. The element is annotated with `@Override`
## Debugging Issues
To rebuild the user-facing documentation without having to rebuild the entire system, use the `ant docs` target. This will run the markdown generator, Jekyll, and copy the resulting HTML into the appengine build tree. You will at least need to have run `ant noplay` or `ant` to have the rest of the build tree set up for testing the changes with `dev_appserver`.
### Generic-looking tooltip
Consider the following code:
```java
@SimpleProperty(description = "foobar")
pubilc void Foo(String foo) {}
@SimpleProperty
public String Foo() { return ""; }
```
The reference documentation will have the description as "Property for Foo" whereas the block tooltip will be "foobar". The reason for this is because
### Tooltip and reference swapped
Consider the following code:
```java
/**
* Javadoc description.
*/
@SimpleProperty
public void Foo(String foo) {}
@SimpleProperty(description = "Tooltip")
public String Foo() { return ""; }
```
The reference documentation will be "Tooltip" and the tooltip will be "Javadoc description." The reason for this is because the first Foo will gain the Javadoc as its tooltip (non-empty Javadoc plus no description provided). The second one
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment