com.google.javascript.rhino.JSDocInfo Maven / Gradle / Ivy
Show all versions of closure-compiler Show documentation
/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
/**
* JSDoc information describing JavaScript code. JSDoc is represented as a unified object with
* fields for each JSDoc annotation, even though some combinations are incorrect. For instance, if a
* JSDoc describes an enum, it cannot have information about a return type. This implementation
* takes advantage of such incompatibilities to reuse fields for multiple purposes, reducing memory
* consumption.
*
* Constructing {@link JSDocInfo} objects is simplified by {@link JSDocInfoBuilder} which
* provides early incompatibility detection.
*
*/
public class JSDocInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Visibility categories. The {@link Visibility#ordinal()} can be used as a
* numerical indicator of privacy, where 0 is the most private. This means
* that the {@link Visibility#compareTo} method can be used to
* determine if a visibility is more permissive than another.
*/
public enum Visibility {
PRIVATE,
PACKAGE,
PROTECTED,
PUBLIC,
// If visibility is not specified, we just assume that visibility
// is inherited from the super class.
INHERITED
}
// Bitfield property indicies.
static class Property {
static final int
NG_INJECT = 0,
WIZ_ACTION = 1,
// Polymer specific
POLYMER_BEHAVIOR = 2,
POLYMER = 3,
CUSTOM_ELEMENT = 4,
MIXIN_CLASS = 5,
MIXIN_FUNCTION = 6;
}
private static final class LazilyInitializedInfo implements Serializable {
private static final long serialVersionUID = 1L;
// Function information
private JSTypeExpression baseType;
private ArrayList extendedInterfaces;
private ArrayList implementedInterfaces;
private LinkedHashMap parameters;
private ArrayList thrownTypes;
private LinkedHashMap templateTypeNames;
private Set disposedParameters;
private LinkedHashMap typeTransformations;
// Other information
private String description;
private String meaning;
private String alternateMessageId;
private String deprecated;
private String license;
private ImmutableSet suppressions;
private ImmutableSet modifies;
private JSTypeExpression lendsName;
@Nullable private String closurePrimitiveId;
// Bit flags for properties.
private int propertyBitField;
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("bitfield", (propertyBitField == 0) ? null : Integer.toHexString(propertyBitField))
.add("baseType", baseType)
.add("extendedInterfaces", extendedInterfaces)
.add("implementedInterfaces", implementedInterfaces)
.add("parameters", parameters)
.add("thrownTypes", thrownTypes)
.add("templateTypeNames", templateTypeNames)
.add("disposedParameters", disposedParameters)
.add("typeTransformations", typeTransformations)
.add("description", description)
.add("meaning", meaning)
.add("deprecated", deprecated)
.add("license", license)
.add("suppressions", suppressions)
.add("modifies", modifies)
.add("lendsName", lendsName)
.add("closurePrimitiveId", closurePrimitiveId)
.omitNullValues()
.toString();
}
@SuppressWarnings("MissingOverride") // Adding @Override breaks the GWT compilation.
protected LazilyInitializedInfo clone() {
return clone(false);
}
protected LazilyInitializedInfo clone(boolean cloneTypeNodes) {
LazilyInitializedInfo other = cloneWithoutTypes();
other.baseType = cloneType(baseType, cloneTypeNodes);
other.extendedInterfaces = cloneTypeList(extendedInterfaces, cloneTypeNodes);
other.implementedInterfaces = cloneTypeList(implementedInterfaces, cloneTypeNodes);
other.parameters = cloneTypeMap(parameters, cloneTypeNodes);
other.thrownTypes = cloneTypeList(thrownTypes, cloneTypeNodes);
other.templateTypeNames = templateTypeNames == null ? null
: new LinkedHashMap<>(templateTypeNames);
other.disposedParameters = disposedParameters == null ? null
: new HashSet<>(disposedParameters);
other.typeTransformations = typeTransformations == null ? null
: new LinkedHashMap<>(typeTransformations);
return other;
}
protected LazilyInitializedInfo cloneWithoutTypes() {
LazilyInitializedInfo other = new LazilyInitializedInfo();
other.description = description;
other.meaning = meaning;
other.deprecated = deprecated;
other.license = license;
other.suppressions = suppressions == null ? null : ImmutableSet.copyOf(suppressions);
other.modifies = modifies == null ? null : ImmutableSet.copyOf(modifies);
other.closurePrimitiveId = closurePrimitiveId;
other.propertyBitField = propertyBitField;
other.alternateMessageId = alternateMessageId;
return other;
}
protected ArrayList cloneTypeList(
ArrayList list, boolean cloneTypeExpressionNodes) {
ArrayList newlist = null;
if (list != null) {
newlist = new ArrayList<>(list.size());
for (JSTypeExpression expr : list) {
newlist.add(cloneType(expr, cloneTypeExpressionNodes));
}
}
return newlist;
}
protected LinkedHashMap cloneTypeMap(
LinkedHashMap map, boolean cloneTypeExpressionNodes) {
LinkedHashMap newmap = null;
if (map != null) {
newmap = new LinkedHashMap<>();
for (Entry entry : map.entrySet()) {
JSTypeExpression value = entry.getValue();
newmap.put(entry.getKey(), cloneType(value, cloneTypeExpressionNodes));
}
}
return newmap;
}
// TODO(nnaze): Consider putting bit-fiddling logic in a reusable
// location.
void setBit(int bitIndex, boolean value) {
int mask = getMaskForBitIndex(bitIndex);
if (value) {
propertyBitField |= mask;
} else {
propertyBitField &= ~mask;
}
}
boolean isBitSet(int bitIndex) {
int mask = getMaskForBitIndex(bitIndex);
return (mask & propertyBitField) != 0;
}
private static int getMaskForBitIndex(int bitIndex) {
checkArgument(bitIndex >= 0, "Bit index should be non-negative integer");
return 1 << bitIndex;
}
}
private static final class LazilyInitializedDocumentation implements Serializable {
private String sourceComment;
private ArrayList markers;
private LinkedHashMap parameters;
private LinkedHashMap throwsDescriptions;
private String blockDescription;
private String fileOverview;
private String returnDescription;
private String version;
private List authors;
private List sees;
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("sourceComment", sourceComment)
.add("markers", markers)
.add("parameters", parameters)
.add("throwsDescriptions", throwsDescriptions)
.add("blockDescription", blockDescription)
.add("fileOverview", fileOverview)
.add("returnDescription", returnDescription)
.add("version", version)
.add("authors", authors)
.add("sees", sees)
.omitNullValues()
.toString();
}
}
/**
* A piece of information (found in a marker) which contains a position
* with a string.
*/
public static class StringPosition extends SourcePosition {
static boolean areEquivalent(StringPosition p1, StringPosition p2) {
if (p1 == null && p2 == null) {
return true;
}
if ((p1 == null && p2 != null) || (p1 != null && p2 == null)) {
return false;
}
return Objects.equals(p1.getItem(), p2.getItem())
&& p1.getStartLine() == p2.getStartLine()
&& p1.getPositionOnStartLine() == p2.getPositionOnStartLine()
&& p1.getEndLine() == p2.getEndLine()
&& p1.getPositionOnEndLine() == p2.getPositionOnEndLine();
}
}
/**
* A piece of information (found in a marker) which contains a position
* with a string that has no leading or trailing whitespace.
*/
static class TrimmedStringPosition extends StringPosition {
@Override public void setItem(String item) {
checkArgument(
item.charAt(0) != ' ' && item.charAt(item.length() - 1) != ' ',
"String has leading or trailing whitespace");
super.setItem(item);
}
}
/**
* A piece of information (found in a marker) which contains a position
* with a name node.
*/
public static class NamePosition extends SourcePosition {
static boolean areEquivalent(NamePosition p1, NamePosition p2) {
if (p1 == null && p2 == null) {
return true;
}
if ((p1 == null && p2 != null) || (p1 != null && p2 == null)) {
return false;
}
if ((p1.getItem() == null && p2.getItem() != null)
|| (p1.getItem() != null && p2.getItem() == null)) {
return false;
}
return ((p1.getItem() == null && p2.getItem() == null)
|| p1.getItem().isEquivalentTo(p2.getItem()))
&& p1.getStartLine() == p2.getStartLine()
&& p1.getPositionOnStartLine() == p2.getPositionOnStartLine()
&& p1.getEndLine() == p2.getEndLine()
&& p1.getPositionOnEndLine() == p2.getPositionOnEndLine();
}
}
/**
* A piece of information (found in a marker) which contains a position
* with a type expression syntax tree.
*/
public static class TypePosition extends SourcePosition {
private boolean brackets = false;
/** Returns whether the type has curly braces around it. */
public boolean hasBrackets() {
return brackets;
}
void setHasBrackets(boolean newVal) {
brackets = newVal;
}
static boolean areEquivalent(TypePosition p1, TypePosition p2) {
if (p1 == null && p2 == null) {
return true;
}
if ((p1 == null && p2 != null) || (p1 != null && p2 == null)) {
return false;
}
if ((p1.getItem() == null && p2.getItem() != null)
|| (p1.getItem() != null && p2.getItem() == null)) {
return false;
}
return ((p1.getItem() == null && p2.getItem() == null)
|| p1.getItem().isEquivalentTo(p2.getItem()))
&& p1.getStartLine() == p2.getStartLine()
&& p1.getPositionOnStartLine() == p2.getPositionOnStartLine()
&& p1.getEndLine() == p2.getEndLine()
&& p1.getPositionOnEndLine() == p2.getPositionOnEndLine()
&& p1.brackets == p2.brackets;
}
}
/**
* Defines a class for containing the parsing information
* for this JSDocInfo. For each annotation found in the
* JsDoc, a marker will be created indicating the annotation
* itself, the name of the annotation (if any; for example,
* a @param has a name, but a @return does not), the
* textual description found on that annotation and, if applicable,
* the type declaration. All this information is only collected
* if documentation collection is turned on.
*/
public static final class Marker {
private TrimmedStringPosition annotation;
private NamePosition nameNode;
private StringPosition description;
private TypePosition type;
/**
* Gets the position information for the annotation name. (e.g., "param")
*/
public StringPosition getAnnotation() {
return annotation;
}
void setAnnotation(TrimmedStringPosition p) {
annotation = p;
}
/**
* Gets the position information for the name found
* in an @param tag.
*/
public NamePosition getNameNode() {
return nameNode;
}
void setNameNode(NamePosition p) {
nameNode = p;
}
/**
* Gets the position information for the description found
* in a block tag.
*/
public StringPosition getDescription() {
return description;
}
void setDescription(StringPosition p) {
description = p;
}
/**
* Gets the position information for the type expression found
* in some block tags, like "@param" and "@return".
*/
public TypePosition getType() {
return type;
}
void setType(TypePosition p) {
type = p;
}
private static boolean areEquivalent(Marker m1, Marker m2) {
if (m1 == null && m2 == null) {
return true;
}
if ((m1 == null && m2 != null) || (m1 != null && m2 == null)) {
return false;
}
return TrimmedStringPosition.areEquivalent(m1.annotation, m2.annotation)
&& NamePosition.areEquivalent(m1.nameNode, m2.nameNode)
&& StringPosition.areEquivalent(m1.description, m2.description)
&& TypePosition.areEquivalent(m1.type, m2.type);
}
}
private LazilyInitializedInfo info;
private LazilyInitializedDocumentation documentation;
private Visibility visibility;
/**
* The {@link #isConstant()}, {@link #isConstructor()}, {@link #isInterface},
* {@link #isHidden()} and other flags as well as
* whether the {@link #type} field stores a value for {@link #getType()},
* {@link #getReturnType()} or {@link #getEnumParameterType()}.
*
* @see #setFlag(boolean, int)
* @see #getFlag(int)
* @see #setType(JSTypeExpression, int)
* @see #getType(int)
*/
private int bitset;
/**
* The type for {@link #getType()}, {@link #getReturnType()} or
* {@link #getEnumParameterType()}. The knowledge of which one is recorded is
* stored in the {@link #bitset} field.
*
* @see #setType(JSTypeExpression, int)
* @see #getType(int)
*/
private JSTypeExpression type;
/**
* The type for {@link #getThisType()}.
*/
private JSTypeExpression thisType;
/**
* Whether the type annotation was inlined.
*/
private boolean inlineType;
/**
* Whether to include documentation.
*
* @see JSDocInfo.LazilyInitializedDocumentation
*/
private boolean includeDocumentation;
/**
* Position of the original comment.
*/
private int originalCommentPosition;
// We use a bit map to represent whether or not the JSDoc contains
// one of the "boolean" annotation types (annotations like @constructor,
// for which the presence of the annotation alone is significant).
// Mask all the boolean annotation types
private static final int MASK_FLAGS = 0x3FFFFFFF;
private static final int MASK_CONSTANT = 0x00000001; // @const
private static final int MASK_CONSTRUCTOR = 0x00000002; // @constructor
private static final int MASK_DEFINE = 0x00000004; // @define
private static final int MASK_HIDDEN = 0x00000008; // @hidden
private static final int MASK_TYPE_SUMMARY = 0x00000010; // @typeSummary
private static final int MASK_FINAL = 0x00000020; // @final
private static final int MASK_OVERRIDE = 0x00000040; // @override
@SuppressWarnings("unused")
private static final int MASK_UNUSED_1 = 0x00000080;
private static final int MASK_DEPRECATED = 0x00000100; // @deprecated
private static final int MASK_INTERFACE = 0x00000200; // @interface
private static final int MASK_EXPORT = 0x00000400; // @export
private static final int MASK_NOINLINE = 0x00000800; // @noinline
private static final int MASK_FILEOVERVIEW = 0x00001000; // @fileoverview
private static final int MASK_IMPLICITCAST = 0x00002000; // @implicitCast
private static final int MASK_NOSIDEEFFECTS = 0x00004000; // @nosideeffects
private static final int MASK_EXTERNS = 0x00008000; // @externs
private static final int MASK_XIDGEN = 0x00010000; // @idGenerator {xid}
private static final int MASK_NOCOMPILE = 0x00020000; // @nocompile
private static final int MASK_CONSISTIDGEN = 0x00040000; // @idGenerator {consistent}
private static final int MASK_IDGEN = 0x00080000; // @idGenerator {unique}
private static final int MASK_EXPOSE = 0x00100000; // @expose
private static final int MASK_UNRESTRICTED = 0x00200000; // @unrestricted
private static final int MASK_STRUCT = 0x00400000; // @struct
private static final int MASK_DICT = 0x00800000; // @dict
private static final int MASK_STABLEIDGEN = 0x01000000; // @idGenerator {stable}
private static final int MASK_MAPPEDIDGEN = 0x02000000; // @idGenerator {mapped}
private static final int MASK_NOCOLLAPSE = 0x04000000; // @nocollapse
private static final int MASK_RECORD = 0x08000000; // @record
private static final int MASK_ABSTRACT = 0x10000000; // @abstract
// No more masks available
// 3 bit type field stored in the top 3 bits of the most significant
// nibble.
private static final int COMPLEMENT_TYPEFIELD = 0x1FFFFFFF; // 0001...
private static final int MASK_TYPEFIELD = 0xE0000000; // 1110...
private static final int TYPEFIELD_TYPE = 0x20000000; // 0010...
private static final int TYPEFIELD_RETURN = 0x40000000; // 0100...
private static final int TYPEFIELD_ENUM = 0x60000000; // 0110...
private static final int TYPEFIELD_TYPEDEF = 0x80000000; // 1000...
/**
* Creates a {@link JSDocInfo} object. This object should be created using
* a {@link JSDocInfoBuilder}.
*/
JSDocInfo(boolean includeDocumentation) {
this.includeDocumentation = includeDocumentation;
}
// Visible for testing.
JSDocInfo() {}
@SuppressWarnings("MissingOverride") // Adding @Override breaks the GWT compilation.
public JSDocInfo clone() {
return clone(false);
}
public JSDocInfo cloneWithNewType(boolean cloneTypeNodes, JSTypeExpression typeExpression) {
JSDocInfo other = clone(cloneTypeNodes);
if (this.hasType()) {
other.bitset = other.bitset & COMPLEMENT_TYPEFIELD; // clear type field bits
other.setType(typeExpression);
}
return other;
}
/**
* Clones this JSDoc but replaces the given names in any type related annotation with unknown
* type.
*
* @return returns the the cloned JSDocInfo
*/
public JSDocInfo cloneAndReplaceTypeNames(Set names) {
JSDocInfo other = cloneWithoutTypes();
Map typeExpressions = this.getTypeExpressionsWithKind();
for (Map.Entry itr : typeExpressions.entrySet()) {
JSTypeExpression typeExpression = itr.getKey();
TypeExpressionKind kind = itr.getValue();
JSTypeExpression newExpr = constructNewTypeExpression(names, typeExpression);
switch (kind) {
case TYPE:
other.setType(newExpr);
break;
case RETURN:
other.setReturnType(newExpr);
break;
case ENUM:
other.setEnumParameterType(newExpr);
break;
case TYPEDEF:
other.setTypedefType(newExpr);
break;
case BASE:
other.setBaseType(newExpr);
break;
case LEND:
other.setLendsName(newExpr);
break;
case THIS:
other.setThisType(newExpr);
break;
case PARAM:
if (this.info != null) {
other.info.parameters = constructNewTypesFromMap(this.info.parameters, names);
}
break;
case TEMPLATE:
if (this.info != null) {
other.info.templateTypeNames =
constructNewTypesFromMap(this.info.templateTypeNames, names);
}
break;
case IMPLEMENTS:
if (this.info != null) {
other.info.implementedInterfaces =
constructNewTypesFromList(this.info.implementedInterfaces, names);
}
break;
case EXTENDS:
if (this.info != null) {
other.info.extendedInterfaces =
constructNewTypesFromList(this.info.extendedInterfaces, names);
}
break;
case THROWS:
if (this.info != null) {
other.info.thrownTypes = constructNewTypesFromList(this.info.thrownTypes, names);
}
break;
}
}
return other;
}
/**
* Removes any module local names from the given JSTypeExpression and replaces them with unknown
*/
private static JSTypeExpression constructNewTypeExpression(
Set names, JSTypeExpression oldTypeExpression) {
if (oldTypeExpression == null) {
return null;
}
JSTypeExpression newTypeExpression = oldTypeExpression.replaceNamesWithUnknownType(names);
return newTypeExpression;
}
/**
* Removes any module local names used in the typeExpressions in any {@code Map} and replaces them with the unknown type. Intended to be used
* for @param, @throws or @templateTypeNames data members that are stored as maps.
*/
private static LinkedHashMap constructNewTypesFromMap(
Map map, Set names) {
if (map == null) {
return null;
}
LinkedHashMap ret = new LinkedHashMap<>();
for (Entry itr : map.entrySet()) {
String key = itr.getKey();
JSTypeExpression typeExpression = itr.getValue();
JSTypeExpression newTypeExpression = constructNewTypeExpression(names, typeExpression);
ret.put(key, newTypeExpression);
}
return ret;
}
/**
* Removes any module local names used in any {@code ArrayList} and replaces
* them with unknown Intended to be used for @implements or @extends that are stored as lists.
*/
private static ArrayList constructNewTypesFromList(
ArrayList arr, Set names) {
if (arr == null) {
return null;
}
ArrayList ret = new ArrayList<>();
for (JSTypeExpression typeExpression : arr) {
JSTypeExpression newTypeExpression = constructNewTypeExpression(names, typeExpression);
ret.add(newTypeExpression);
}
return ret;
}
public JSDocInfo clone(boolean cloneTypeNodes) {
JSDocInfo other = new JSDocInfo();
other.info = this.info == null ? null : this.info.clone(cloneTypeNodes);
other.documentation = this.documentation;
other.visibility = this.visibility;
other.bitset = this.bitset;
other.type = cloneType(this.type, cloneTypeNodes);
other.thisType = cloneType(this.thisType, cloneTypeNodes);
other.includeDocumentation = this.includeDocumentation;
other.originalCommentPosition = this.originalCommentPosition;
return other;
}
private JSDocInfo cloneWithoutTypes() {
JSDocInfo other = new JSDocInfo();
other.info = this.info == null ? null : this.info.cloneWithoutTypes();
other.documentation = this.documentation;
other.visibility = this.visibility;
other.bitset = this.bitset & COMPLEMENT_TYPEFIELD;
other.includeDocumentation = this.includeDocumentation;
other.originalCommentPosition = this.originalCommentPosition;
return other;
}
private static JSTypeExpression cloneType(JSTypeExpression expr, boolean cloneTypeNodes) {
if (expr != null) {
return cloneTypeNodes ? expr.copy() : expr;
}
return null;
}
@VisibleForTesting
public static boolean areEquivalent(JSDocInfo jsDoc1, JSDocInfo jsDoc2) {
if (jsDoc1 == null && jsDoc2 == null) {
return true;
}
if (jsDoc1 == null || jsDoc2 == null) {
return false;
}
if (!Objects.equals(jsDoc1.getParameterNames(), jsDoc2.getParameterNames())) {
return false;
}
for (String param : jsDoc1.getParameterNames()) {
if (!Objects.equals(jsDoc1.getParameterType(param), jsDoc2.getParameterType(param))) {
return false;
}
}
if (jsDoc1.getMarkers().size() != jsDoc2.getMarkers().size()) {
return false;
}
Iterator it1 = jsDoc1.getMarkers().iterator();
Iterator it2 = jsDoc2.getMarkers().iterator();
while (it1.hasNext()) {
if (!Marker.areEquivalent(it1.next(), it2.next())) {
return false;
}
}
return Objects.equals(jsDoc1.getAuthors(), jsDoc2.getAuthors())
&& Objects.equals(jsDoc1.getBaseType(), jsDoc2.getBaseType())
&& Objects.equals(jsDoc1.getBlockDescription(), jsDoc2.getBlockDescription())
&& Objects.equals(jsDoc1.getFileOverview(), jsDoc2.getFileOverview())
&& Objects.equals(jsDoc1.getImplementedInterfaces(), jsDoc2.getImplementedInterfaces())
&& Objects.equals(jsDoc1.getEnumParameterType(), jsDoc2.getEnumParameterType())
&& Objects.equals(jsDoc1.getExtendedInterfaces(), jsDoc2.getExtendedInterfaces())
&& Objects.equals(jsDoc1.getLendsName(), jsDoc2.getLendsName())
&& Objects.equals(jsDoc1.getLicense(), jsDoc2.getLicense())
&& Objects.equals(jsDoc1.getMeaning(), jsDoc2.getMeaning())
&& Objects.equals(jsDoc1.getModifies(), jsDoc2.getModifies())
&& Objects.equals(jsDoc1.getOriginalCommentString(), jsDoc2.getOriginalCommentString())
&& (jsDoc1.getPropertyBitField() == jsDoc2.getPropertyBitField())
&& Objects.equals(jsDoc1.getReferences(), jsDoc2.getReferences())
&& Objects.equals(jsDoc1.getReturnDescription(), jsDoc2.getReturnDescription())
&& Objects.equals(jsDoc1.getReturnType(), jsDoc2.getReturnType())
&& Objects.equals(jsDoc1.getSuppressions(), jsDoc2.getSuppressions())
&& Objects.equals(jsDoc1.getTemplateTypeNames(), jsDoc2.getTemplateTypeNames())
&& Objects.equals(jsDoc1.getThisType(), jsDoc2.getThisType())
&& Objects.equals(jsDoc1.getThrownTypes(), jsDoc2.getThrownTypes())
&& Objects.equals(jsDoc1.getTypedefType(), jsDoc2.getTypedefType())
&& Objects.equals(jsDoc1.getType(), jsDoc2.getType())
&& Objects.equals(jsDoc1.getVersion(), jsDoc2.getVersion())
&& Objects.equals(jsDoc1.getVisibility(), jsDoc2.getVisibility())
&& Objects.equals(jsDoc1.getClosurePrimitiveId(), jsDoc2.getClosurePrimitiveId())
&& Objects.equals(jsDoc1.getAlternateMessageId(), jsDoc2.getAlternateMessageId())
&& jsDoc1.bitset == jsDoc2.bitset;
}
boolean isDocumentationIncluded() {
return includeDocumentation;
}
void setConsistentIdGenerator(boolean value) {
setFlag(value, MASK_CONSISTIDGEN);
}
void setStableIdGenerator(boolean value) {
setFlag(value, MASK_STABLEIDGEN);
}
void setXidGenerator(boolean value) {
setFlag(value, MASK_XIDGEN);
}
void setMappedIdGenerator(boolean value) {
setFlag(value, MASK_MAPPEDIDGEN);
}
void setConstant(boolean value) {
setFlag(value, MASK_CONSTANT);
}
void setFinal(boolean value) {
setFlag(value, MASK_FINAL);
}
void setConstructor(boolean value) {
setFlag(value, MASK_CONSTRUCTOR);
}
void setAbstract() {
setFlag(true, MASK_ABSTRACT);
}
void setUnrestricted() {
setFlag(true, MASK_UNRESTRICTED);
}
void setStruct() {
setStruct(true);
}
void setStruct(boolean value) {
setFlag(value, MASK_STRUCT);
}
void setDict() {
setFlag(true, MASK_DICT);
}
void setDefine(boolean value) {
setFlag(value, MASK_DEFINE);
}
void setHidden(boolean value) {
setFlag(value, MASK_HIDDEN);
}
void setOverride(boolean value) {
setFlag(value, MASK_OVERRIDE);
}
void setDeprecated(boolean value) {
setFlag(value, MASK_DEPRECATED);
}
void setInterface(boolean value) {
setFlag(value, MASK_INTERFACE);
}
void setExport(boolean value) {
setFlag(value, MASK_EXPORT);
}
void setExpose(boolean value) {
setFlag(value, MASK_EXPOSE);
}
void setIdGenerator(boolean value) {
setFlag(value, MASK_IDGEN);
}
void setImplicitCast(boolean value) {
setFlag(value, MASK_IMPLICITCAST);
}
void setNoSideEffects(boolean value) {
setFlag(value, MASK_NOSIDEEFFECTS);
}
void setExterns(boolean value) {
setFlag(value, MASK_EXTERNS);
}
void setTypeSummary(boolean value) {
setFlag(value, MASK_TYPE_SUMMARY);
}
void setNoCompile(boolean value) {
setFlag(value, MASK_NOCOMPILE);
}
void setNoCollapse(boolean value) {
setFlag(value, MASK_NOCOLLAPSE);
}
void setNoInline(boolean value) {
setFlag(value, MASK_NOINLINE);
}
private void setFlag(boolean value, int mask) {
if (value) {
bitset |= mask;
} else {
bitset &= ~mask;
}
}
void setImplicitMatch(boolean value) {
setFlag(value, MASK_RECORD);
}
/**
* @return whether the {@code @idGenerator {consistent}} is present on
* this {@link JSDocInfo}
*/
public boolean isConsistentIdGenerator() {
return getFlag(MASK_CONSISTIDGEN);
}
/**
* @return whether the {@code @idGenerator {stable}} is present on this {@link JSDocInfo}.
*/
public boolean isStableIdGenerator() {
return getFlag(MASK_STABLEIDGEN);
}
/**
* @return whether the {@code @idGenerator {xid}} is present on this {@link JSDocInfo}.
*/
public boolean isXidGenerator() {
return getFlag(MASK_XIDGEN);
}
/**
* @return whether the {@code @idGenerator {mapped}} is present on this {@link JSDocInfo}.
*/
public boolean isMappedIdGenerator() {
return getFlag(MASK_MAPPEDIDGEN);
}
/**
* @return whether this {@link JSDocInfo} implies that annotated value is constant.
*/
public boolean isConstant() {
// @desc is used with goog.getMsg to define mesages to be translated,
// and thus must be @const in order for translation to work correctly.
return getFlag(MASK_CONSTANT | MASK_DEFINE | MASK_FINAL) || getDescription() != null;
}
/**
* Returns whether the {@code @const} annotation is present on this {@link JSDocInfo}.
*/
public boolean hasConstAnnotation() {
return getFlag(MASK_CONSTANT);
}
/**
* Returns whether the {@code @final} annotation is present on this {@link JSDocInfo}.
*/
public boolean isFinal() {
return getFlag(MASK_FINAL);
}
/**
* Returns whether the {@code @constructor} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isConstructor() {
return getFlag(MASK_CONSTRUCTOR);
}
/**
* Returns whether the {@code @abstract} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isAbstract() {
return getFlag(MASK_ABSTRACT);
}
/**
* Returns whether the {@code @record} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean usesImplicitMatch() {
return getFlag(MASK_RECORD);
}
/**
* Returns whether the {@code @unrestricted} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean makesUnrestricted() {
return getFlag(MASK_UNRESTRICTED);
}
/**
* Returns whether the {@code @struct} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean makesStructs() {
return getFlag(MASK_STRUCT);
}
/**
* Returns whether the {@code @dict} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean makesDicts() {
return getFlag(MASK_DICT);
}
/**
* Returns whether the {@code @define} annotation is present on this
* {@link JSDocInfo}. If this annotation is present, then the
* {@link #getType()} method will retrieve the define type.
*/
public boolean isDefine() {
return getFlag(MASK_DEFINE);
}
/**
* Returns whether the {@code @hidden} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isHidden() {
return getFlag(MASK_HIDDEN);
}
/**
* Returns whether the {@code @override} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isOverride() {
return getFlag(MASK_OVERRIDE);
}
/**
* Returns whether the {@code @deprecated} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isDeprecated() {
return getFlag(MASK_DEPRECATED);
}
/**
* Returns whether the {@code @interface} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isInterface() {
return getFlag(MASK_INTERFACE) || getFlag(MASK_RECORD);
}
public boolean isConstructorOrInterface() {
return isConstructor() || isInterface();
}
/**
* Returns whether the {@code @export} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isExport() {
return getFlag(MASK_EXPORT);
}
/**
* Returns whether the {@code @expose} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isExpose() {
return getFlag(MASK_EXPOSE);
}
/**
* @return whether the {@code @idGenerator} is present on
* this {@link JSDocInfo}
*/
public boolean isIdGenerator() {
return getFlag(MASK_IDGEN);
}
/**
* Returns whether the {@code @implicitCast} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isImplicitCast() {
return getFlag(MASK_IMPLICITCAST);
}
/**
* Returns whether the {@code @nosideeffects} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isNoSideEffects() {
return getFlag(MASK_NOSIDEEFFECTS);
}
/**
* Returns whether the {@code @externs} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isExterns() {
return getFlag(MASK_EXTERNS);
}
/**
* Returns whether the {@code @typeSummary} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isTypeSummary() {
return getFlag(MASK_TYPE_SUMMARY);
}
/**
* Returns whether the {@code @nocompile} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isNoCompile() {
return getFlag(MASK_NOCOMPILE);
}
/**
* Returns whether the {@code @nocollapse} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isNoCollapse() {
return getFlag(MASK_NOCOLLAPSE);
}
/**
* Returns whether the {@code @noinline} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isNoInline() {
return getFlag(MASK_NOINLINE);
}
/**
* Returns whether there is a declaration present on this {@link JSDocInfo}.
*
* Does not consider `@const` (without a following type) to indicate a declaration. Whether you
* want this method, or the`containsDeclaration` that includes const, depends on whether you want
* to consider {@code /** @const * / a.b.c = 0} a declaration or not.
*/
public boolean containsDeclarationExcludingTypelessConst() {
return (hasType()
|| hasReturnType()
|| hasEnumParameterType()
|| hasTypedefType()
|| hasThisType()
|| getParameterCount() > 0
|| getImplementedInterfaceCount() > 0
|| hasBaseType()
|| visibility != Visibility.INHERITED
|| getFlag(
MASK_CONSTRUCTOR
| MASK_DEFINE
| MASK_OVERRIDE
| MASK_EXPORT
| MASK_EXPOSE
| MASK_DEPRECATED
| MASK_INTERFACE
| MASK_IMPLICITCAST
| MASK_NOSIDEEFFECTS
| MASK_RECORD));
}
private boolean hasParamType() {
return getParameterCount() != 0;
}
/** Returns whether this JSDoc contains a type declaration such as {@code /** @type {string}} */
public boolean containsTypeDeclaration() {
return hasType()
|| hasReturnType()
|| hasEnumParameterType()
|| hasTypedefType()
|| hasThisType()
|| hasBaseType()
|| hasParamType();
}
/**
* Returns whether there is a declaration present on this {@link JSDocInfo}, including a
* typeless @const like {@code /** @const * / a.b.c = 0}
*/
public boolean containsDeclaration() {
return containsDeclarationExcludingTypelessConst() || getFlag(MASK_CONSTANT);
}
/**
* @deprecated This method is quite heuristic, looking for @type annotations that start with
* "function". Other methods like containsDeclaration() and containsTypeDefinition are generally
* preferred.
* @return Whether there is a declaration of a callable type.
*/
@Deprecated
public boolean containsFunctionDeclaration() {
boolean hasFunctionType = hasType() && getType().getRoot().isFunction();
return hasFunctionType
|| hasReturnType()
|| hasThisType()
|| getParameterCount() > 0
|| getFlag(MASK_CONSTRUCTOR)
|| (getFlag(MASK_NOSIDEEFFECTS) && !hasType());
}
// For jsdocs that create new types. Not to be confused with jsdocs that
// declare the type of a variable or property.
public boolean containsTypeDefinition() {
return isConstructor() || isInterface()
|| hasEnumParameterType() || hasTypedefType();
}
private boolean getFlag(int mask) {
return (bitset & mask) != 0x00;
}
void setVisibility(Visibility visibility) {
this.visibility = visibility;
}
private void lazyInitInfo() {
if (info == null) {
info = new LazilyInitializedInfo();
}
}
/**
* Lazily initializes the documentation information object, but only
* if the JSDocInfo was told to keep such information around.
*/
private boolean lazyInitDocumentation() {
if (!includeDocumentation) {
return false;
}
if (documentation == null) {
documentation = new LazilyInitializedDocumentation();
}
return true;
}
/** @return whether the {@code @code} is present within this {@link JSDocInfo}. */
public boolean isAtSignCodePresent() {
final String entireComment = getOriginalCommentString();
return (entireComment == null) ? false : entireComment.contains("@code");
}
/**
* Adds a marker to the documentation (if it exists) and
* returns the marker. Returns null otherwise.
*/
Marker addMarker() {
if (!lazyInitDocumentation()) {
return null;
}
if (documentation.markers == null) {
documentation.markers = new ArrayList<>();
}
Marker marker = new Marker();
documentation.markers.add(marker);
return marker;
}
/**
* Sets the deprecation reason.
*
* @param reason The deprecation reason
*/
boolean setDeprecationReason(String reason) {
lazyInitInfo();
if (info.deprecated != null) {
return false;
}
info.deprecated = reason;
return true;
}
/**
* Add a suppressed warning.
*/
void addSuppression(String suppression) {
lazyInitInfo();
if (info.suppressions == null) {
info.suppressions = ImmutableSet.of(suppression);
} else {
info.suppressions = new ImmutableSet.Builder()
.addAll(info.suppressions)
.add(suppression)
.build();
}
}
/**
* Adds a set of suppressions to the (possibly currently empty) set of suppressions.
* @param suppressions A list of suppressed warning types.
*/
void addSuppressions(Set suppressions) {
lazyInitInfo();
if (info.suppressions != null) {
suppressions = Sets.union(suppressions, info.suppressions);
}
info.suppressions = ImmutableSet.copyOf(suppressions);
}
/**
* Sets modifies values.
* @param modifies A list of modifies types.
*/
boolean setModifies(Set modifies) {
lazyInitInfo();
if (info.modifies != null) {
return false;
}
info.modifies = ImmutableSet.copyOf(modifies);
return true;
}
/**
* Documents the version.
*/
boolean documentVersion(String version) {
if (!lazyInitDocumentation()) {
return true;
}
if (documentation.version != null) {
return false;
}
documentation.version = version;
return true;
}
/**
* Documents a reference (i.e. adds a "see" reference to the list).
*/
boolean documentReference(String reference) {
if (!lazyInitDocumentation()) {
return true;
}
if (documentation.sees == null) {
documentation.sees = new ArrayList<>();
}
documentation.sees.add(reference);
return true;
}
/**
* Documents the author (i.e. adds it to the author list).
*/
boolean documentAuthor(String author) {
if (!lazyInitDocumentation()) {
return true;
}
if (documentation.authors == null) {
documentation.authors = new ArrayList<>();
}
documentation.authors.add(author);
return true;
}
/**
* Documents the throws (i.e. adds it to the throws list).
*/
boolean documentThrows(JSTypeExpression type, String throwsDescription) {
if (!lazyInitDocumentation()) {
return true;
}
if (documentation.throwsDescriptions == null) {
documentation.throwsDescriptions = new LinkedHashMap<>();
}
if (!documentation.throwsDescriptions.containsKey(type)) {
documentation.throwsDescriptions.put(type, throwsDescription);
return true;
}
return false;
}
/**
* Documents a parameter. Parameters are described using the {@code @param}
* annotation.
*
* @param parameter the parameter's name
* @param description the parameter's description
*/
boolean documentParam(String parameter, String description) {
if (!lazyInitDocumentation()) {
return true;
}
if (documentation.parameters == null) {
documentation.parameters = Maps.newLinkedHashMapWithExpectedSize(1);
}
if (!documentation.parameters.containsKey(parameter)) {
documentation.parameters.put(parameter, description);
return true;
} else {
return false;
}
}
/**
* Documents the block-level comment/description.
*
* @param description the description
*/
boolean documentBlock(String description) {
if (!lazyInitDocumentation()) {
return true;
}
if (documentation.blockDescription != null) {
return false;
}
documentation.blockDescription = description;
return true;
}
/**
* Documents the fileoverview comment/description.
*
* @param description the description
*/
boolean documentFileOverview(String description) {
setFlag(true, MASK_FILEOVERVIEW);
if (!lazyInitDocumentation()) {
return true;
}
if (documentation.fileOverview != null) {
return false;
}
documentation.fileOverview = description;
return true;
}
/**
* Documents the return value. Return value is described using the
* {@code @return} annotation.
*
* @param description the return value's description
*/
boolean documentReturn(String description) {
if (!lazyInitDocumentation()) {
return true;
}
if (documentation.returnDescription != null) {
return false;
}
documentation.returnDescription = description;
return true;
}
/**
* Declares a parameter. Parameters are described using the {@code @param}
* annotation.
*
* @param jsType the parameter's type, it may be {@code null} when the
* {@code @param} annotation did not specify a type.
* @param parameter the parameter's name
*/
boolean declareParam(JSTypeExpression jsType, String parameter) {
lazyInitInfo();
if (info.parameters == null) {
info.parameters = Maps.newLinkedHashMapWithExpectedSize(1);
}
if (!info.parameters.containsKey(parameter)) {
info.parameters.put(parameter, jsType);
return true;
} else {
return false;
}
}
/**
* Declares a template type name. Template type names are described using the {@code @template}
* annotation.
*
* @param newTemplateTypeName the template type name.
*/
boolean declareTemplateTypeName(String newTemplateTypeName) {
lazyInitInfo();
return declareTemplateTypeName(newTemplateTypeName, null);
}
boolean declareTemplateTypeName(
String newTemplateTypeName, JSTypeExpression newTemplateTypeBound) {
lazyInitInfo();
newTemplateTypeBound =
newTemplateTypeBound == null
? JSTypeExpression.IMPLICIT_TEMPLATE_BOUND
: newTemplateTypeBound;
if (isTypeTransformationName(newTemplateTypeName) || hasTypedefType()) {
return false;
}
if (info.templateTypeNames == null) {
info.templateTypeNames = Maps.newLinkedHashMapWithExpectedSize(1);
} else if (info.templateTypeNames.containsKey(newTemplateTypeName)) {
return false;
}
info.templateTypeNames.put(newTemplateTypeName, newTemplateTypeBound);
return true;
}
private boolean isTemplateTypeName(String name) {
if (info.templateTypeNames == null) {
return false;
}
return info.templateTypeNames.containsKey(name);
}
private boolean isTypeTransformationName(String name) {
if (info.typeTransformations == null) {
return false;
}
return info.typeTransformations.containsKey(name);
}
/**
* Declares a type transformation expression. These expressions are described
* using a {@code @template} annotation of the form
* {@code @template T := TTL-Expr =:}
*
* @param newName The name associated to the type transformation.
* @param expr The type transformation expression.
*/
boolean declareTypeTransformation(String newName, Node expr) {
lazyInitInfo();
if (isTemplateTypeName(newName)) {
return false;
}
if (info.typeTransformations == null){
// A LinkedHashMap is used to keep the insertion order. The type
// transformation expressions will be evaluated in this order.
info.typeTransformations = Maps.newLinkedHashMapWithExpectedSize(1);
} else if (info.typeTransformations.containsKey(newName)) {
return false;
}
info.typeTransformations.put(newName, expr);
return true;
}
/**
* Declares that the method throws a given type.
*
* @param jsType The type that can be thrown by the method.
*/
boolean declareThrows(JSTypeExpression jsType) {
lazyInitInfo();
if (info.thrownTypes == null) {
info.thrownTypes = new ArrayList<>();
}
info.thrownTypes.add(jsType);
return true;
}
/**
* Gets the visibility specified by {@code @private}, {@code @protected} or
* {@code @public} annotation. If no visibility is specified, visibility
* is inherited from the base class.
*/
public Visibility getVisibility() {
return visibility;
}
/**
* Gets the type of a given named parameter.
* @param parameter the parameter's name
* @return the parameter's type or {@code null} if this parameter is not
* defined or has a {@code null} type
*/
public JSTypeExpression getParameterType(String parameter) {
if (info == null || info.parameters == null) {
return null;
}
return info.parameters.get(parameter);
}
/**
* Returns whether the parameter is defined.
*/
public boolean hasParameter(String parameter) {
if (info == null || info.parameters == null) {
return false;
}
return info.parameters.containsKey(parameter);
}
/**
* Returns whether the parameter has an attached type.
*
* @return {@code true} if the parameter has an attached type, {@code false}
* if the parameter has no attached type or does not exist.
*/
public boolean hasParameterType(String parameter) {
return getParameterType(parameter) != null;
}
/**
* Returns the set of names of the defined parameters. The iteration order
* of the returned set is the order in which parameters are defined in the
* JSDoc, rather than the order in which the function declares them.
*
* @return the set of names of the defined parameters. The returned set is
* immutable.
*/
public Set getParameterNames() {
if (info == null || info.parameters == null) {
return ImmutableSet.of();
}
return ImmutableSet.copyOf(info.parameters.keySet());
}
/**
* Returns the nth name in the defined parameters. The iteration order
* is the order in which parameters are defined in the JSDoc, rather
* than the order in which the function declares them.
*/
public String getParameterNameAt(int index) {
if (info == null || info.parameters == null) {
return null;
}
if (index >= info.parameters.size()) {
return null;
}
return Iterables.get(info.parameters.keySet(), index);
}
/**
* Gets the number of parameters defined.
*/
public int getParameterCount() {
if (info == null || info.parameters == null) {
return 0;
}
return info.parameters.size();
}
void setInlineType() {
this.inlineType = true;
}
void setReturnType(JSTypeExpression type) {
setType(type, TYPEFIELD_RETURN);
}
void setTypedefType(JSTypeExpression type) {
setType(type, TYPEFIELD_TYPEDEF);
}
void setEnumParameterType(JSTypeExpression type) {
setType(type, TYPEFIELD_ENUM);
}
boolean declareTypedefType(JSTypeExpression type) {
if (getTemplateTypeNames().isEmpty()) {
setType(type, TYPEFIELD_TYPEDEF);
return true;
}
return false;
}
void setType(JSTypeExpression type) {
setType(type, TYPEFIELD_TYPE);
}
private void setType(JSTypeExpression type, int mask) {
if ((bitset & MASK_TYPEFIELD) != 0) {
throw new IllegalStateException(
"API tried to add two incompatible type tags. "
+ "This should have been blocked and emitted a warning.");
}
this.bitset = (bitset & MASK_FLAGS) | mask;
this.type = type;
}
/**
* Returns the list of thrown types.
*/
public List getThrownTypes() {
if (info == null || info.thrownTypes == null) {
return ImmutableList.of();
}
return Collections.unmodifiableList(info.thrownTypes);
}
/**
* Get the message for a given thrown type.
*/
public String getThrowsDescriptionForType(JSTypeExpression type) {
if (documentation == null || documentation.throwsDescriptions == null) {
return null;
}
return documentation.throwsDescriptions.get(type);
}
/**
* Returns whether an enum parameter type, specified using the {@code @enum}
* annotation, is present on this JSDoc.
*/
public boolean hasEnumParameterType() {
return hasType(TYPEFIELD_ENUM);
}
/**
* Returns whether a typedef parameter type, specified using the
* {@code @typedef} annotation, is present on this JSDoc.
*/
public boolean hasTypedefType() {
return hasType(TYPEFIELD_TYPEDEF);
}
/**
* Returns whether this {@link JSDocInfo} contains a type for {@code @return}
* annotation.
*/
public boolean hasReturnType() {
return hasType(TYPEFIELD_RETURN);
}
/**
* Returns whether a type, specified using the {@code @type} annotation, is
* present on this JSDoc.
*/
public boolean hasType() {
return hasType(TYPEFIELD_TYPE);
}
private boolean hasType(int mask) {
return (bitset & MASK_TYPEFIELD) == mask;
}
public boolean hasTypeInformation() {
return (bitset & MASK_TYPEFIELD) != 0;
}
/**
* Returns whether the type annotation was inlined.
*/
public boolean isInlineType() {
return inlineType;
}
/**
* Gets the return type specified by the {@code @return} annotation.
*/
public JSTypeExpression getReturnType() {
return getType(TYPEFIELD_RETURN);
}
/**
* Gets the enum parameter type specified by the {@code @enum} annotation.
*/
public JSTypeExpression getEnumParameterType() {
return getType(TYPEFIELD_ENUM);
}
/**
* Gets the typedef type specified by the {@code @type} annotation.
*/
public JSTypeExpression getTypedefType() {
return getType(TYPEFIELD_TYPEDEF);
}
/**
* Gets the type specified by the {@code @type} annotation.
*/
public JSTypeExpression getType() {
return getType(TYPEFIELD_TYPE);
}
private JSTypeExpression getType(int typefield) {
if ((MASK_TYPEFIELD & bitset) == typefield) {
return type;
} else {
return null;
}
}
/**
* Gets the type specified by the {@code @this} annotation.
*/
public JSTypeExpression getThisType() {
return thisType;
}
/**
* Sets the type specified by the {@code @this} annotation.
*/
void setThisType(JSTypeExpression type) {
this.thisType = type;
}
/**
* Returns whether this {@link JSDocInfo} contains a type for {@code @this}
* annotation.
*/
public boolean hasThisType() {
return thisType != null;
}
void setBaseType(JSTypeExpression type) {
lazyInitInfo();
info.baseType = type;
}
/**
* Gets the base type specified by the {@code @extends} annotation.
*/
public JSTypeExpression getBaseType() {
return (info == null) ? null : info.baseType;
}
/**
* Gets the description specified by the {@code @desc} annotation.
*/
public String getDescription() {
return (info == null) ? null : info.description;
}
void setDescription(String desc) {
lazyInitInfo();
info.description = desc;
}
/**
* Gets the meaning specified by the {@code @meaning} annotation.
*
* In localization systems, two messages with the same content but
* different "meanings" may be translated differently. By default, we
* use the name of the variable that the message is initialized to as
* the "meaning" of the message.
*
*
But some code generators (like Closure Templates) inject their own
* meaning with the jsdoc {@code @meaning} annotation.
*/
public String getMeaning() {
return (info == null) ? null : info.meaning;
}
void setMeaning(String meaning) {
lazyInitInfo();
info.meaning = meaning;
}
/**
* Gets the alternate message ID specified by the {@code @alternateMessageId} annotation.
*
*
In localization systems, if we migrate from one message ID algorithm to another, we can
* specify the old one via {@code @alternateMessageId}. This allows the product to use the
* previous translation while waiting for the new one to be translated.
*
*
Some code generators (like Closure Templates) inject this.
*/
public String getAlternateMessageId() {
return (info == null) ? null : info.alternateMessageId;
}
void setAlternateMessageId(String alternateMessageId) {
lazyInitInfo();
info.alternateMessageId = alternateMessageId;
}
/**
* Gets the name we're lending to in a {@code @lends} annotation.
*
*
In many reflection APIs, you pass an anonymous object to a function, and that function mixes
* the anonymous object into another object. The {@code @lends} annotation allows the type system
* to track those property assignments.
*/
public JSTypeExpression getLendsName() {
return (info == null) ? null : info.lendsName;
}
void setLendsName(JSTypeExpression name) {
lazyInitInfo();
info.lendsName = name;
}
public boolean hasLendsName() {
return getLendsName() != null;
}
void setClosurePrimitiveId(String closurePrimitiveId) {
lazyInitInfo();
info.closurePrimitiveId = closurePrimitiveId;
}
/** Returns the {@code @closurePrimitive {id}} identifier */
public String getClosurePrimitiveId() {
return (info == null) ? null : info.closurePrimitiveId;
}
/** Whether this JSDoc is annotated with {@code @closurePrimitive} */
public boolean hasClosurePrimitiveId() {
return getClosurePrimitiveId() != null;
}
/**
* Returns whether JSDoc is annotated with {@code @ngInject} annotation.
*/
public boolean isNgInject() {
return (info != null) && info.isBitSet(Property.NG_INJECT);
}
void setNgInject(boolean ngInject) {
lazyInitInfo();
info.setBit(Property.NG_INJECT, ngInject);
}
/**
* Returns whether JSDoc is annotated with {@code @wizaction} annotation.
*/
public boolean isWizaction() {
return (info != null) && info.isBitSet(Property.WIZ_ACTION);
}
void setWizaction(boolean wizaction) {
lazyInitInfo();
info.setBit(Property.WIZ_ACTION, wizaction);
}
/**
* Returns whether JSDoc is annotated with {@code @polymerBehavior} annotation.
*/
public boolean isPolymerBehavior() {
return (info != null) && info.isBitSet(Property.POLYMER_BEHAVIOR);
}
void setPolymerBehavior(boolean polymerBehavior) {
lazyInitInfo();
info.setBit(Property.POLYMER_BEHAVIOR, polymerBehavior);
}
/** Returns whether JSDoc is annotated with {@code @polymer} annotation. */
public boolean isPolymer() {
return (info != null) && info.isBitSet(Property.POLYMER);
}
void setPolymer(boolean polymer) {
lazyInitInfo();
info.setBit(Property.POLYMER, polymer);
}
/** Returns whether JSDoc is annotated with {@code @customElement} annotation. */
public boolean isCustomElement() {
return (info != null) && info.isBitSet(Property.CUSTOM_ELEMENT);
}
void setCustomElement(boolean customElement) {
lazyInitInfo();
info.setBit(Property.CUSTOM_ELEMENT, customElement);
}
/** Returns whether JSDoc is annotated with {@code @mixinClass} annotation. */
public boolean isMixinClass() {
return (info != null) && info.isBitSet(Property.MIXIN_CLASS);
}
void setMixinClass(boolean mixinClass) {
lazyInitInfo();
info.setBit(Property.MIXIN_CLASS, mixinClass);
}
/** Returns whether JSDoc is annotated with {@code @mixinFunction} annotation. */
public boolean isMixinFunction() {
return (info != null) && info.isBitSet(Property.MIXIN_FUNCTION);
}
void setMixinFunction(boolean mixinFunction) {
lazyInitInfo();
info.setBit(Property.MIXIN_FUNCTION, mixinFunction);
}
/** Returns whether JSDoc is annotated with {@code @disposes} annotation. */
public boolean isDisposes() {
return (info == null) ? false : info.disposedParameters != null;
}
boolean setDisposedParameter(String parameterName) {
lazyInitInfo();
// Lazily initialize disposedParameters
if (info.disposedParameters == null) {
info.disposedParameters = new HashSet<>();
}
if (info.disposedParameters.contains(parameterName)) {
return false;
} else {
info.disposedParameters.add(parameterName);
return true;
}
}
/**
* Return whether the function disposes of specified parameter.
*/
public boolean disposesOf(String parameterName) {
return isDisposes() && info.disposedParameters.contains(parameterName);
}
/**
* Gets the description specified by the {@code @license} annotation.
*/
public String getLicense() {
return (info == null) ? null : info.license;
}
/**
* @param license String containing new license text.
*/
void setLicense(String license) {
lazyInitInfo();
info.license = license;
}
@Override
public String toString() {
return "JSDocInfo";
}
@VisibleForTesting
public String toStringVerbose() {
return MoreObjects.toStringHelper(this)
.add("bitset", (bitset == 0) ? null : Integer.toHexString(bitset))
.add("documentation", documentation)
.add("info", info)
.add("originalComment", getOriginalCommentString())
.add("thisType", thisType)
.add("type", type)
.add("visibility", visibility)
.omitNullValues()
.toString();
}
/**
* Returns whether this {@link JSDocInfo} contains a type for {@code @extends}
* annotation.
*/
public boolean hasBaseType() {
return getBaseType() != null;
}
/**
* Adds an implemented interface. Returns whether the interface was added. If
* the interface was already present in the list, it won't get added again.
*/
boolean addImplementedInterface(JSTypeExpression interfaceName) {
lazyInitInfo();
if (info.implementedInterfaces == null) {
info.implementedInterfaces = new ArrayList<>(2);
}
if (info.implementedInterfaces.contains(interfaceName)) {
return false;
}
info.implementedInterfaces.add(interfaceName);
return true;
}
/**
* Returns the types specified by the {@code @implements} annotation.
*
* @return An immutable list of JSTypeExpression objects that can
* be resolved to types.
*/
public List getImplementedInterfaces() {
if (info == null || info.implementedInterfaces == null) {
return ImmutableList.of();
}
return Collections.unmodifiableList(info.implementedInterfaces);
}
/**
* Gets the number of interfaces specified by the {@code @implements}
* annotation.
*/
public int getImplementedInterfaceCount() {
if (info == null || info.implementedInterfaces == null) {
return 0;
}
return info.implementedInterfaces.size();
}
/**
* Adds an extended interface (for interface only).
* Returns whether the type was added.
* if the type was already present in the list, it won't get added again.
*/
boolean addExtendedInterface(JSTypeExpression type) {
lazyInitInfo();
if (info.extendedInterfaces == null) {
info.extendedInterfaces = new ArrayList<>(2);
}
if (info.extendedInterfaces.contains(type)) {
return false;
}
info.extendedInterfaces.add(type);
return true;
}
/**
* Returns the interfaces extended by an interface
*
* @return An immutable list of JSTypeExpression objects that can
* be resolved to types.
*/
public List getExtendedInterfaces() {
if (info == null || info.extendedInterfaces == null) {
return ImmutableList.of();
}
return Collections.unmodifiableList(info.extendedInterfaces);
}
/**
* Gets the number of extended interfaces specified
*/
public int getExtendedInterfacesCount() {
if (info == null || info.extendedInterfaces == null) {
return 0;
}
return info.extendedInterfaces.size();
}
/**
* Returns the deprecation reason or null if none specified.
*/
public String getDeprecationReason() {
return info == null ? null : info.deprecated;
}
/**
* Returns the set of suppressed warnings.
*/
public Set getSuppressions() {
Set suppressions = info == null ? null : info.suppressions;
return suppressions == null ? Collections.emptySet() : suppressions;
}
/**
* Returns the set of sideeffect notations.
*/
public Set getModifies() {
Set modifies = info == null ? null : info.modifies;
return modifies == null ? Collections.emptySet() : modifies;
}
private int getPropertyBitField() {
return info == null ? 0 : info.propertyBitField;
}
void mergePropertyBitfieldFrom(JSDocInfo other) {
if (other.info != null) {
lazyInitInfo();
info.propertyBitField |= other.getPropertyBitField();
}
}
/**
* Returns whether a description exists for the parameter with the specified
* name.
*/
public boolean hasDescriptionForParameter(String name) {
if (documentation == null || documentation.parameters == null) {
return false;
}
return documentation.parameters.containsKey(name);
}
/**
* Returns the description for the parameter with the given name, if its
* exists.
*/
public String getDescriptionForParameter(String name) {
if (documentation == null || documentation.parameters == null) {
return null;
}
return documentation.parameters.get(name);
}
/**
* Returns the list of authors or null if none.
*/
public List getAuthors() {
return documentation == null ? null : documentation.authors;
}
/**
* Returns the list of references or null if none.
*/
public List getReferences() {
return documentation == null ? null : documentation.sees;
}
/**
* Returns the version or null if none.
*/
public String getVersion() {
return documentation == null ? null : documentation.version;
}
/**
* Returns the description of the returned object or null if none specified.
*/
public String getReturnDescription() {
return documentation == null ? null : documentation.returnDescription;
}
/**
* Returns the block-level description or null if none specified.
*/
public String getBlockDescription() {
return documentation == null ? null : documentation.blockDescription;
}
/**
* Returns whether this has a fileoverview flag.
*/
public boolean hasFileOverview() {
return getFlag(MASK_FILEOVERVIEW);
}
/**
* Returns the file overview or null if none specified.
*/
public String getFileOverview() {
return documentation == null ? null : documentation.fileOverview;
}
/** Gets the list of all markers for the documentation in this JSDoc. */
public Collection getMarkers() {
return (documentation == null || documentation.markers == null)
? ImmutableList.of() : documentation.markers;
}
/**
* Gets the @template type names.
*
* Excludes @template types from TTL; get those with {@link #getTypeTransformations()}
*/
public ImmutableList getTemplateTypeNames() {
if (info == null || info.templateTypeNames == null) {
return ImmutableList.of();
}
return ImmutableList.copyOf(info.templateTypeNames.keySet());
}
public ImmutableMap getTemplateTypes() {
if (info == null || info.templateTypeNames == null) {
return ImmutableMap.of();
}
return ImmutableMap.copyOf(info.templateTypeNames);
}
/** Gets the type transformations. */
public ImmutableMap getTypeTransformations() {
if (info == null || info.typeTransformations == null) {
return ImmutableMap.of();
}
return ImmutableMap.copyOf(info.typeTransformations);
}
// What kind of type expression this JSTypeExpression represents
private enum TypeExpressionKind {
BASE,
ENUM,
EXTENDS,
IMPLEMENTS,
LEND,
PARAM,
RETURN,
TEMPLATE,
THIS,
THROWS,
TYPE,
TYPEDEF,
}
/**
* Finds type expressions within the JsDoc and returns them with their kind. The kind of type
* expression can be:
*
*
* - BASE
*
- ENUM
*
- EXTENDS
*
- IMPLEMENTS
*
- LEND
*
- PARAM
*
- RETURN
*
- THIS
*
- THROWS
*
- TYPE
*
- TYPEDEF,
*
*/
private Map getTypeExpressionsWithKind() {
Map nodes = new LinkedHashMap<>();
if (type != null) {
if (hasType()) {
nodes.put(type, TypeExpressionKind.TYPE);
} else if (hasEnumParameterType()) {
nodes.put(type, TypeExpressionKind.ENUM);
} else if (hasReturnType()) {
nodes.put(type, TypeExpressionKind.RETURN);
} else if (hasTypedefType()) {
nodes.put(type, TypeExpressionKind.TYPEDEF);
} else {
throw new IllegalStateException(
"type field holds none of @type, @enum, @return or @typedef types");
}
}
if (thisType != null) {
nodes.put(thisType, TypeExpressionKind.THIS);
}
if (info != null) {
if (info.baseType != null) {
nodes.put(info.baseType, TypeExpressionKind.BASE);
}
if (info.extendedInterfaces != null) {
for (JSTypeExpression interfaceType : info.extendedInterfaces) {
if (interfaceType != null) {
nodes.put(interfaceType, TypeExpressionKind.EXTENDS);
}
}
}
if (info.implementedInterfaces != null) {
for (JSTypeExpression interfaceType : info.implementedInterfaces) {
if (interfaceType != null) {
nodes.put(interfaceType, TypeExpressionKind.IMPLEMENTS);
}
}
}
if (info.parameters != null) {
for (JSTypeExpression parameterType : info.parameters.values()) {
if (parameterType != null) {
nodes.put(parameterType, TypeExpressionKind.PARAM);
}
}
}
if (info.thrownTypes != null) {
for (JSTypeExpression thrownType : info.thrownTypes) {
if (thrownType != null) {
nodes.put(thrownType, TypeExpressionKind.THROWS);
}
}
}
if (info.lendsName != null) {
nodes.put(info.lendsName, TypeExpressionKind.LEND);
}
if (info.templateTypeNames != null) {
for (JSTypeExpression upperBound : info.templateTypeNames.values()) {
if (upperBound != null) {
nodes.put(upperBound, TypeExpressionKind.TEMPLATE);
}
}
}
}
return nodes;
}
/**
* Returns a collection of all JSTypeExpressions that are a part of this JSDocInfo.
*
* This includes:
*
*
* - base type
*
- @extends
*
- @implements
*
- @lend
*
- @param
*
- @return
*
- @template
*
- @this
*
- @throws
*
- @type
*
*
* Any future type specific JSDoc should make sure to add the appropriate nodes here.
*
* @return collection of all type nodes
*/
public Collection getTypeExpressions() {
return getTypeExpressionsWithKind().keySet();
}
/**
* Returns a collection of all type nodes that are a part of this JSDocInfo.
*
* This includes:
*
*
* - @extends
*
- @implements
*
- @lend
*
- @param
*
- @return
*
- @template
*
- @this
*
- @throws
*
- @type
*
*
* Any future type specific JSDoc should make sure to add the appropriate nodes here.
*
* @return collection of all type nodes
*/
public Collection getTypeNodes() {
List nodes = new ArrayList<>();
for (JSTypeExpression type : getTypeExpressions()) {
nodes.add(type.getRoot());
}
return nodes;
}
public boolean hasModifies() {
return info != null && info.modifies != null;
}
/**
* Returns the original JSDoc comment string. Returns null unless
* parseJsDocDocumentation is enabled via the ParserConfig.
*/
public String getOriginalCommentString() {
return documentation == null ? null : documentation.sourceComment;
}
void setOriginalCommentString(String sourceComment) {
if (!lazyInitDocumentation()) {
return;
}
documentation.sourceComment = sourceComment;
}
public int getOriginalCommentPosition() {
return originalCommentPosition;
}
void setOriginalCommentPosition(int position) {
originalCommentPosition = position;
}
/** Get the value of the @modifies{this} annotation stored in the doc info. */
public boolean modifiesThis() {
return (this.getModifies().contains("this"));
}
/** @return Whether the @modifies annotation includes "arguments" or any named parameters. */
public boolean hasSideEffectsArgumentsAnnotation() {
Set modifies = this.getModifies();
// TODO(johnlenz): if we start tracking parameters individually
// this should simply be a check for "arguments".
return (modifies.size() > 1 || (modifies.size() == 1 && !modifies.contains("this")));
}
}