Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.google.javascript.rhino.JSDocInfo Maven / Gradle / Ivy
/*
*
* ***** 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 static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.nullToEmpty;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CaseFormat;
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 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.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Supplier;
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 JSDocInfo.Builder} which
* provides early incompatibility detection.
*/
public class JSDocInfo implements Serializable {
private static final long serialVersionUID = 1L;
private static class Property implements Comparable> {
private static int bitCounter = 0;
static final Property>[] values = new Property>[64];
final String name;
final int bit;
final long mask;
private Property(String name) {
this.name = name;
this.bit = bitCounter++;
this.mask = 1L << this.bit;
if (this.bit > 63) {
throw new AssertionError("Too many Properties");
}
values[this.bit] = this;
}
@Nullable
@SuppressWarnings("unchecked") // cast to T is unsafe but guaranteed by builder
T get(JSDocInfo info) {
if ((info.propertyKeysBitset & mask) == 0) {
return null;
}
return (T) info.getPropertyValueByIndex(Long.bitCount(info.propertyKeysBitset & (mask - 1)));
}
T clone(T arg, @Nullable TypeTransform transform) {
return arg;
}
boolean isDefault(T value) {
return value == null
|| value == Visibility.INHERITED
|| (value instanceof Collection && ((Collection>) value).isEmpty())
|| (value instanceof Map && ((Map, ?>) value).isEmpty());
}
boolean equalValues(T left, T right) {
return left.equals(right);
}
Iterable getTypeExpressions(T value) {
return ImmutableList.of();
}
// NOTE: These need to be comparable so that we can iterate over them to build the list.
@Override
public int compareTo(Property that) {
return this.bit - that.bit;
}
}
private static final class MarkerListProperty extends Property> {
MarkerListProperty(String name) {
super(name);
}
@Override
boolean equalValues(ArrayList a, ArrayList b) {
if (a.size() != b.size()) {
return false;
}
for (int i = 0; i < a.size(); i++) {
Marker m1 = a.get(i);
Marker m2 = b.get(i);
if ((m1 == null) != (m2 == null) || (m1 != null && !m1.isEquivalentTo(m2))) {
return false;
}
}
return true;
}
// NOTE: documentation props are never cloned
}
private static final class TypeProperty extends Property {
TypeProperty(String name) {
super(name);
}
@Override
JSTypeExpression clone(JSTypeExpression arg, TypeTransform transform) {
return arg != null && transform != null ? transform.apply(arg) : arg;
}
@Override
boolean equalValues(JSTypeExpression left, JSTypeExpression right) {
return left.isEquivalentTo(right);
}
@Override
ImmutableList getTypeExpressions(JSTypeExpression type) {
return ImmutableList.of(type);
}
}
private static final class TypeListProperty extends Property> {
TypeListProperty(String name) {
super(name);
}
List getUnmodifiable(JSDocInfo info) {
ArrayList value = get(info);
return value != null ? Collections.unmodifiableList(value) : ImmutableList.of();
}
@Override
ArrayList clone(ArrayList arg, TypeTransform transform) {
ArrayList out = new ArrayList<>(arg);
if (transform != null) {
for (int i = 0; i < out.size(); i++) {
JSTypeExpression elem = out.get(i);
if (elem != null) {
out.set(i, transform.apply(elem));
}
}
}
return out;
}
@Override
boolean equalValues(ArrayList left, ArrayList right) {
if (left.size() != right.size()) {
return false;
}
final Iterator leftIterator = left.iterator();
final Iterator rightIterator = right.iterator();
while (leftIterator.hasNext()) {
final JSTypeExpression leftExpr = leftIterator.next();
final JSTypeExpression rightExpr = rightIterator.next();
if (!leftExpr.isEquivalentTo(rightExpr)) {
return false;
}
}
return true;
}
@Override
Iterable getTypeExpressions(ArrayList types) {
return types;
}
}
private static final class TypeMapProperty
extends Property> {
TypeMapProperty(String name) {
super(name);
}
@Override
LinkedHashMap clone(
LinkedHashMap arg, TypeTransform transform) {
LinkedHashMap out = new LinkedHashMap<>(arg);
if (transform != null) {
for (Map.Entry entry : out.entrySet()) {
JSTypeExpression elem = entry.getValue();
if (elem != null) {
entry.setValue(transform.apply(elem));
}
}
}
return out;
}
@Override
boolean equalValues(
LinkedHashMap left,
LinkedHashMap right) {
final Set leftKeys = left.keySet();
final Set rightKeys = right.keySet();
if (!leftKeys.equals(rightKeys)) {
return false;
}
for (String key : leftKeys) {
final JSTypeExpression leftExpr = left.get(key);
final JSTypeExpression rightExpr = right.get(key);
if (!areEquivalent(leftExpr, rightExpr)) {
return false;
}
}
return true;
}
private boolean areEquivalent(JSTypeExpression expr1, JSTypeExpression expr2) {
return Objects.equals(expr1, expr2) || (expr1 != null && expr1.isEquivalentTo(expr2));
}
@Override
Iterable getTypeExpressions(LinkedHashMap map) {
return map.values();
}
}
private static final class NodeMapProperty extends Property> {
NodeMapProperty(String name) {
super(name);
}
@Override
LinkedHashMap clone(LinkedHashMap arg, TypeTransform transform) {
return new LinkedHashMap<>(arg);
}
@Override
boolean equalValues(LinkedHashMap left, LinkedHashMap right) {
if (left.size() != right.size()) {
return false;
}
for (Map.Entry entry : left.entrySet()) {
Node rightValue = right.get(entry.getKey());
if (rightValue == null || !entry.getValue().isEquivalentTo(rightValue)) {
return false;
}
}
return true;
}
}
private static enum Bit {
/** Whether the type annotation was inlined. */
INLINE_TYPE,
/** Whether to include documentation. */
INCLUDE_DOCUMENTATION,
// The following are all straightforward annotations.
CONST,
CONSTRUCTOR,
DEFINE,
HIDDEN,
TYPE_SUMMARY,
FINAL,
OVERRIDE,
DEPRECATED,
INTERFACE,
EXPORT,
ENHANCED_NAMESPACE,
NOINLINE,
FILEOVERVIEW,
IMPLICITCAST,
NOSIDEEFFECTS,
EXTERNS,
NOCOMPILE,
UNRESTRICTED,
STRUCT,
DICT,
NOCOLLAPSE,
RECORD,
ABSTRACT,
PURE_OR_BREAK_MY_CODE,
COLLAPSIBLE_OR_BREAK_MY_CODE,
NG_INJECT,
WIZ_ACTION,
POLYMER_BEHAVIOR,
POLYMER,
CUSTOM_ELEMENT,
MIXIN_CLASS,
MIXIN_FUNCTION,
// `@provideGoog` only appears in base.js
PROVIDE_GOOG,
PROVIDE_ALREADY_PROVIDED,
WIZ_CALLBACK;
final String name;
final long mask;
private Bit() {
if (ordinal() > 63) {
throw new AssertionError("Too many Bits");
}
this.name = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name());
this.mask = 1L << ordinal();
}
}
@FunctionalInterface
private interface TypeTransform {
JSTypeExpression apply(JSTypeExpression arg);
}
private final long propertyBits;
private final long propertyKeysBitset;
/**
* This value will be `null` if there is no property, a literal value if there is only one, or an
* `Object[]` if there are multiple.
*
* This is meant to elide eager allocation of single-valued or empty lists.
*/
@Nullable private final Object propertyValues;
@SuppressWarnings("unchecked")
private JSDocInfo(long bits, TreeMap, Object> props) {
long keys = 0;
List values = new ArrayList<>();
for (Map.Entry, Object> entry : props.entrySet()) {
Property prop = (Property) entry.getKey();
Object value = entry.getValue();
if (!prop.isDefault(value)) {
keys |= prop.mask;
// Ensure that another codepath did not inadvertently retrieve a reference to the array
// or a `null` value.
checkState(!(value instanceof Object[]) && (value != null));
values.add(value);
}
}
this.propertyBits = bits;
this.propertyKeysBitset = keys;
this.propertyValues = packPropertyValues(values);
}
private boolean checkBit(Bit bit) {
return (propertyBits & bit.mask) != 0;
}
private TreeMap, Object> asMap() {
TreeMap, Object> map = new TreeMap<>();
long bits = propertyKeysBitset;
int index = 0;
while (bits > 0) {
int low = Long.numberOfTrailingZeros(bits);
bits &= ~(1L << low);
map.put(Property.values[low], getPropertyValueByIndex(index++));
}
return map;
}
/**
* 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. This should never be included
// in the map of an actual built JSDocInfo (though it may be in the
// builder), but will be returned by default if none is specified.
INHERITED,
}
private enum IdGenerator {
XID,
CONSISTENT,
UNIQUE,
STABLE,
MAPPED,
}
private static final Property VISIBILITY = new Property<>("visibility");
private static final TypeProperty TYPE = new TypeProperty("type");
private static final TypeProperty RETURN_TYPE = new TypeProperty("returnType");
private static final TypeProperty ENUM_PARAMETER_TYPE = new TypeProperty("enumParameterType");
private static final TypeProperty TYPEDEF_TYPE = new TypeProperty("typedefType");
private static final TypeProperty THIS_TYPE = new TypeProperty("thisType");
private static final Property ORIGINAL_COMMENT_POSITION =
new Property<>("originalCommentPosition");
private static final Property ID_GENERATOR = new Property<>("idGenerator");
private static final TypeProperty BASE_TYPE = new TypeProperty("baseType");
private static final TypeListProperty EXTENDED_INTERFACES =
new TypeListProperty("extendedInterfaces");
private static final TypeListProperty IMPLEMENTED_INTERFACES =
new TypeListProperty("extendedInterfaces");
private static final TypeMapProperty PARAMETERS = new TypeMapProperty("parameters");
private static final Property> THROWS_ANNOTATIONS = new Property<>("throws");
private static final TypeMapProperty TEMPLATE_TYPE_NAMES =
new TypeMapProperty("templateTypeNames");
private static final NodeMapProperty TYPE_TRANSFORMATIONS =
new NodeMapProperty("typeTransformations");
private static final Property DESCRIPTION = new Property<>("description");
private static final Property MEANING = new Property<>("meaning");
private static final Property ALTERNATE_MESSAGE_ID = new Property<>("alternateMessageId");
private static final Property DEPRECATION_REASON = new Property<>("deprecationReason");
private static final Property LICENSE = new Property<>("license");
// For example, `@suppress {first, second} Some description applying to the set (of both first and
// second).`
// For example, `@suppress {first} Some description applying to only to first.`
private static final Property, String>> SUPPRESSIONS =
new Property<>("suppressions");
private static final Property> MODIFIES = new Property<>("modifies");
private static final TypeProperty LENDS_NAME = new TypeProperty("lendsName");
private static final Property CLOSURE_PRIMITIVE_ID = new Property<>("closurePrimitiveId");
// NOTE: The following properties are "documentation properties", which do _not_ get deeply copied
// when JSDocInfo is cloned (i.e. none of these Properties override {@link Property#clone}).
private static final Property SOURCE_COMMENT = new Property<>("sourceComment");
private static final Property> MARKERS = new MarkerListProperty("markers");
private static final Property> PARAMETER_DESCRIPTIONS =
new Property<>("parameterDescriptions");
private static final Property BLOCK_DESCRIPTION = new Property<>("blockDescription");
private static final Property FILEOVERVIEW_DESCRIPTION =
new Property<>("fileoverviewDescription");
private static final Property RETURN_DESCRIPTION = new Property<>("returnDescription");
private static final Property ENHANCED_NAMESPACE = new Property<>("enhance");
private static final Property> TS_TYPES = new Property<>("tsType");
private static final Property> AUTHORS = new Property<>("authors");
private static final Property> SEES = new Property<>("sees");
private abstract static class ComparableSourcePosition extends SourcePosition {
abstract boolean isEquivalentTo(SourcePosition that);
}
/** A piece of information (found in a marker) which contains a position with a string. */
public static class StringPosition extends ComparableSourcePosition {
@Override
boolean isEquivalentTo(SourcePosition that) {
return that != null && isSamePositionAs(that) && Objects.equals(getItem(), that.getItem());
}
}
/**
* 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 ComparableSourcePosition {
@Override
boolean isEquivalentTo(SourcePosition that) {
if (that == null
|| !isSamePositionAs(that)
|| (getItem() == null) != (that.getItem() == null)) {
return false;
}
return getItem() == null || getItem().isEquivalentTo(that.getItem());
}
}
/**
* A piece of information (found in a marker) which contains a position with a type expression
* syntax tree.
*/
public static class TypePosition extends ComparableSourcePosition {
private boolean brackets = false;
/** Returns whether the type has curly braces around it. */
public boolean hasBrackets() {
return brackets;
}
void setHasBrackets(boolean newVal) {
brackets = newVal;
}
@Override
boolean isEquivalentTo(SourcePosition that) {
if (!(that instanceof TypePosition)
|| !isSamePositionAs(that)
|| brackets != ((TypePosition) that).brackets
|| (getItem() == null) != (that.getItem() == null)) {
return false;
}
return getItem() == null || getItem().isEquivalentTo(that.getItem());
}
}
/**
* 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 boolean isEquivalentTo(Marker that) {
return areEquivalent(this.annotation, that.annotation)
&& areEquivalent(this.nameNode, that.nameNode)
&& areEquivalent(this.description, that.description)
&& areEquivalent(this.type, that.type);
}
private static boolean areEquivalent(
@Nullable ComparableSourcePosition p1, @Nullable ComparableSourcePosition p2) {
return (p1 == null) == (p2 == null) && (p1 == null || p1.isEquivalentTo(p2));
}
}
/** Create a new JSDocInfo.Builder object. */
public static Builder builder() {
return new Builder();
}
public Builder toBuilder() {
return toBuilder(null);
}
@SuppressWarnings("unchecked")
private Builder toBuilder(TypeTransform transform) {
Builder builder = new Builder();
// inline type isn't copied...?
builder.bits = propertyBits & ~Bit.INLINE_TYPE.mask;
builder.props = asMap();
builder.populated = true;
for (Map.Entry, Object> entry : builder.props.entrySet()) {
entry.setValue(((Property) entry.getKey()).clone(entry.getValue(), transform));
}
return builder;
}
@SuppressWarnings("MissingOverride") // Adding @Override breaks the GWT compilation.
public JSDocInfo clone() {
return clone(false);
}
/**
* 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) {
return toBuilder((type) -> type.replaceNamesWithUnknownType(names)).build();
}
public JSDocInfo clone(boolean cloneTypeNodes) {
return cloneTypeNodes ? toBuilder(JSTypeExpression::copy).build() : toBuilder().build();
}
@VisibleForTesting
@SuppressWarnings("unchecked")
public static boolean areEquivalent(JSDocInfo jsDoc1, JSDocInfo jsDoc2) {
if ((jsDoc1 == null) != (jsDoc2 == null)) {
return false;
} else if (jsDoc1 == null) {
return true;
}
if (((jsDoc1.propertyBits ^ jsDoc2.propertyBits) & ~EQUIVALENCE_IGNORED_BITS) != 0) {
return false;
}
if (jsDoc1.propertyKeysBitset != jsDoc2.propertyKeysBitset) {
return false;
}
long bits = jsDoc1.propertyKeysBitset;
int index = 0;
while (bits > 0) {
int low = Long.numberOfTrailingZeros(bits);
bits &= ~(1L << low);
Property prop = (Property) Property.values[low];
Object a = jsDoc1.getPropertyValueByIndex(index);
Object b = jsDoc2.getPropertyValueByIndex(index++);
if ((a == null) != (b == null) || (a != null && !prop.equalValues(a, b))) {
return false;
}
}
return true;
}
// NOTE: includeDocumentation and inlineType are not part of equivalence.
private static final long EQUIVALENCE_IGNORED_BITS =
Bit.INCLUDE_DOCUMENTATION.mask | Bit.INLINE_TYPE.mask;
boolean isDocumentationIncluded() {
return checkBit(Bit.INCLUDE_DOCUMENTATION);
}
/** Returns whether any {@code @idGenerator} or {@code idGenerator {?}} annotation is present. */
public boolean isAnyIdGenerator() {
return (propertyKeysBitset & ID_GENERATOR.mask) != 0;
}
/** Returns whether the {@code @idGenerator {consistent}} is present on this {@link JSDocInfo}. */
public boolean isConsistentIdGenerator() {
return ID_GENERATOR.get(this) == IdGenerator.CONSISTENT;
}
/** Returns whether the {@code @idGenerator {stable}} is present on this {@link JSDocInfo}. */
public boolean isStableIdGenerator() {
return ID_GENERATOR.get(this) == IdGenerator.STABLE;
}
/** Returns whether the {@code @idGenerator {xid}} is present on this {@link JSDocInfo}. */
public boolean isXidGenerator() {
return ID_GENERATOR.get(this) == IdGenerator.XID;
}
/** Returns whether the {@code @idGenerator {mapped}} is present on this {@link JSDocInfo}. */
public boolean isMappedIdGenerator() {
return ID_GENERATOR.get(this) == IdGenerator.MAPPED;
}
/** Returns whether the {@code @idGenerator} is present on this {@link JSDocInfo}. */
public boolean isIdGenerator() {
return ID_GENERATOR.get(this) == IdGenerator.UNIQUE;
}
/** Returns whether this {@link JSDocInfo} implies that annotated value is constant. */
public boolean isConstant() {
// @desc is used with goog.getMsg to define messages to be translated,
// and thus must be @const in order for translation to work correctly.
return (propertyBits & (Bit.CONST.mask | Bit.DEFINE.mask | Bit.FINAL.mask)) != 0
|| (propertyKeysBitset & DESCRIPTION.mask) != 0;
}
/** Returns whether the {@code @const} annotation is present on this {@link JSDocInfo}. */
public boolean hasConstAnnotation() {
return checkBit(Bit.CONST);
}
/** Returns whether the {@code @final} annotation is present on this {@link JSDocInfo}. */
public boolean isFinal() {
return checkBit(Bit.FINAL);
}
/** Returns whether the {@code @constructor} annotation is present on this {@link JSDocInfo}. */
public boolean isConstructor() {
return checkBit(Bit.CONSTRUCTOR);
}
/** Returns whether the {@code @abstract} annotation is present on this {@link JSDocInfo}. */
public boolean isAbstract() {
return checkBit(Bit.ABSTRACT);
}
/** Returns whether the {@code @record} annotation is present on this {@link JSDocInfo}. */
public boolean usesImplicitMatch() {
return checkBit(Bit.RECORD);
}
/** Returns whether the {@code @unrestricted} annotation is present on this {@link JSDocInfo}. */
public boolean makesUnrestricted() {
return checkBit(Bit.UNRESTRICTED);
}
/** Returns whether the {@code @struct} annotation is present on this {@link JSDocInfo}. */
public boolean makesStructs() {
return checkBit(Bit.STRUCT);
}
/** Returns whether the {@code @dict} annotation is present on this {@link JSDocInfo}. */
public boolean makesDicts() {
return checkBit(Bit.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 checkBit(Bit.DEFINE);
}
/** Returns whether the {@code @hidden} annotation is present on this {@link JSDocInfo}. */
public boolean isHidden() {
return checkBit(Bit.HIDDEN);
}
/** Returns whether the {@code @override} annotation is present on this {@link JSDocInfo}. */
public boolean isOverride() {
return checkBit(Bit.OVERRIDE);
}
/** Returns whether the {@code @deprecated} annotation is present on this {@link JSDocInfo}. */
public boolean isDeprecated() {
return checkBit(Bit.DEPRECATED);
}
/** Returns whether the {@code @interface} annotation is present on this {@link JSDocInfo}. */
public boolean isInterface() {
return (propertyBits & (Bit.INTERFACE.mask | Bit.RECORD.mask)) != 0;
}
public boolean isConstructorOrInterface() {
return (propertyBits & (Bit.CONSTRUCTOR.mask | Bit.INTERFACE.mask | Bit.RECORD.mask)) != 0;
}
/** Returns whether the {@code @export} annotation is present on this {@link JSDocInfo}. */
public boolean isExport() {
return checkBit(Bit.EXPORT);
}
/** Returns whether the {@code @implicitCast} annotation is present on this {@link JSDocInfo}. */
public boolean isImplicitCast() {
return checkBit(Bit.IMPLICITCAST);
}
/** Returns whether the {@code @nosideeffects} annotation is present on this {@link JSDocInfo}. */
public boolean isNoSideEffects() {
return checkBit(Bit.NOSIDEEFFECTS);
}
/** Returns whether the {@code @externs} annotation is present on this {@link JSDocInfo}. */
public boolean isExterns() {
return checkBit(Bit.EXTERNS);
}
/** Returns whether the {@code @typeSummary} annotation is present on this {@link JSDocInfo}. */
public boolean isTypeSummary() {
return checkBit(Bit.TYPE_SUMMARY);
}
/** Returns whether the {@code @nocompile} annotation is present on this {@link JSDocInfo}. */
public boolean isNoCompile() {
return checkBit(Bit.NOCOMPILE);
}
/** Returns whether the {@code @nocollapse} annotation is present on this {@link JSDocInfo}. */
public boolean isNoCollapse() {
return checkBit(Bit.NOCOLLAPSE);
}
/** Returns whether the {@code @noinline} annotation is present on this {@link JSDocInfo}. */
public boolean isNoInline() {
return checkBit(Bit.NOINLINE);
}
/**
* Returns whether the {@code @collapsibleOrBreakMyCode} annotation is present on this {@link
* JSDocInfo}.
*/
public boolean isCollapsibleOrBreakMyCode() {
return checkBit(Bit.COLLAPSIBLE_OR_BREAK_MY_CODE);
}
/**
* Returns whether the {@code @pureOrBreakMyCode} annotation is present on this {@link JSDocInfo}.
*/
public boolean isPureOrBreakMyCode() {
return checkBit(Bit.PURE_OR_BREAK_MY_CODE);
}
/** Returns whether the {@code @provideGoog} annotation is present on this {@link JSDocInfo}. */
public boolean isProvideGoog() {
return checkBit(Bit.PROVIDE_GOOG);
}
/**
* Returns whether the {@code @provideAlreadyProvided} annotation is present on this {@link
* JSDocInfo}.
*/
public boolean isProvideAlreadyProvided() {
return checkBit(Bit.PROVIDE_ALREADY_PROVIDED);
}
/**
* 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 (propertyBits
& (Bit.CONSTRUCTOR.mask
| Bit.DEFINE.mask
| Bit.OVERRIDE.mask
| Bit.EXPORT.mask
| Bit.DEPRECATED.mask
| Bit.INTERFACE.mask
| Bit.IMPLICITCAST.mask
| Bit.NOSIDEEFFECTS.mask
| Bit.RECORD.mask))
!= 0
|| (propertyKeysBitset
& (TYPE.mask
| RETURN_TYPE.mask
| ENUM_PARAMETER_TYPE.mask
| TYPEDEF_TYPE.mask
| THIS_TYPE.mask
| PARAMETERS.mask
| IMPLEMENTED_INTERFACES.mask
| BASE_TYPE.mask
| VISIBILITY.mask))
!= 0;
}
/** Returns whether this JSDoc contains a type declaration such as {@code /** @type {string}} */
public boolean containsTypeDeclaration() {
return (propertyKeysBitset
& (TYPE.mask
| RETURN_TYPE.mask
| ENUM_PARAMETER_TYPE.mask
| TYPEDEF_TYPE.mask
| THIS_TYPE.mask
| PARAMETERS.mask
| BASE_TYPE.mask))
!= 0;
}
/**
* 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() || checkBit(Bit.CONST);
}
/**
* @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() {
if (checkBit(Bit.CONSTRUCTOR)
|| (propertyKeysBitset & (RETURN_TYPE.mask | THIS_TYPE.mask | PARAMETERS.mask)) != 0) {
return true;
}
JSTypeExpression type = TYPE.get(this);
if (type != null) {
return type.getRoot().isFunction();
}
return checkBit(Bit.NOSIDEEFFECTS);
}
// 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 (propertyBits & (Bit.CONSTRUCTOR.mask | Bit.INTERFACE.mask)) != 0
|| (propertyKeysBitset & (ENUM_PARAMETER_TYPE.mask | TYPEDEF_TYPE.mask)) != 0;
}
/** Returns whether the {@code @code} is present within this {@link JSDocInfo}. */
public boolean isAtSignCodePresent() {
final String entireComment = getOriginalCommentString();
return (entireComment == null) ? false : entireComment.contains("@code");
}
/**
* 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() {
Visibility visibility = VISIBILITY.get(this);
return visibility != null ? visibility : Visibility.INHERITED;
}
/**
* 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) {
LinkedHashMap params = PARAMETERS.get(this);
return params != null ? params.get(parameter) : null;
}
/** Returns whether the parameter is defined. */
public boolean hasParameter(String parameter) {
LinkedHashMap params = PARAMETERS.get(this);
return params != null && params.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() {
LinkedHashMap params = PARAMETERS.get(this);
return params != null ? ImmutableSet.copyOf(params.keySet()) : ImmutableSet.of();
}
/**
* 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) {
LinkedHashMap params = PARAMETERS.get(this);
if (params == null || index >= params.size()) {
return null;
}
return Iterables.get(params.keySet(), index);
}
/** Gets the number of parameters defined. */
public int getParameterCount() {
LinkedHashMap params = PARAMETERS.get(this);
return params != null ? params.size() : 0;
}
/** Returns the list of thrown types and descriptions as text. */
public List getThrowsAnnotations() {
List annotations = THROWS_ANNOTATIONS.get(this);
return annotations != null ? annotations : ImmutableList.of();
}
/**
* Returns whether an enum parameter type, specified using the {@code @enum} annotation, is
* present on this JSDoc.
*/
public boolean hasEnumParameterType() {
return ENUM_PARAMETER_TYPE.get(this) != null;
}
/**
* Returns whether a typedef parameter type, specified using the {@code @typedef} annotation, is
* present on this JSDoc.
*/
public boolean hasTypedefType() {
return TYPEDEF_TYPE.get(this) != null;
}
/** Returns whether this {@link JSDocInfo} contains a type for {@code @return} annotation. */
public boolean hasReturnType() {
return RETURN_TYPE.get(this) != null;
}
/**
* Returns whether a type, specified using the {@code @type} annotation, is present on this JSDoc.
*/
public boolean hasType() {
return TYPE.get(this) != null;
}
public boolean hasTypeInformation() {
return (propertyKeysBitset
& (TYPE.mask | TYPEDEF_TYPE.mask | ENUM_PARAMETER_TYPE.mask | RETURN_TYPE.mask))
!= 0;
}
/** Returns whether the type annotation was inlined. */
public boolean isInlineType() {
return checkBit(Bit.INLINE_TYPE);
}
/** Gets the return type specified by the {@code @return} annotation. */
public JSTypeExpression getReturnType() {
return RETURN_TYPE.get(this);
}
/** Gets the enum parameter type specified by the {@code @enum} annotation. */
public JSTypeExpression getEnumParameterType() {
return ENUM_PARAMETER_TYPE.get(this);
}
/** Gets the typedef type specified by the {@code @type} annotation. */
public JSTypeExpression getTypedefType() {
return TYPEDEF_TYPE.get(this);
}
/** Gets the type specified by the {@code @type} annotation. */
public JSTypeExpression getType() {
return TYPE.get(this);
}
/** Gets the type specified by the {@code @this} annotation. */
public JSTypeExpression getThisType() {
return THIS_TYPE.get(this);
}
/** Returns whether this {@link JSDocInfo} contains a type for {@code @this} annotation. */
public boolean hasThisType() {
return THIS_TYPE.get(this) != null;
}
/** Gets the base type specified by the {@code @extends} annotation. */
public JSTypeExpression getBaseType() {
return BASE_TYPE.get(this);
}
/** Gets the description specified by the {@code @desc} annotation. */
public String getDescription() {
return DESCRIPTION.get(this);
}
/** Gets the ts type declarations specified by the {@code @tsType} annotations. */
public ImmutableList getTsTypes() {
List types = TS_TYPES.get(this);
return types != null ? ImmutableList.copyOf(types) : ImmutableList.of();
}
/**
* 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 MEANING.get(this);
}
/**
* 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 ALTERNATE_MESSAGE_ID.get(this);
}
/**
* 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 LENDS_NAME.get(this);
}
public boolean hasLendsName() {
return getLendsName() != null;
}
/** Returns the {@code @closurePrimitive {id}} identifier */
public String getClosurePrimitiveId() {
return CLOSURE_PRIMITIVE_ID.get(this);
}
/** Whether this JSDoc is annotated with {@code @closurePrimitive} */
public boolean hasClosurePrimitiveId() {
return CLOSURE_PRIMITIVE_ID.get(this) != null;
}
/** Returns whether JSDoc is annotated with {@code @ngInject} annotation. */
public boolean isNgInject() {
return checkBit(Bit.NG_INJECT);
}
/** Returns whether JSDoc is annotated with {@code @wizaction} annotation. */
public boolean isWizaction() {
return checkBit(Bit.WIZ_ACTION);
}
/** Returns if JSDoc is annotated with {@code @wizcallback} annotation. */
public boolean isWizcallback() {
return checkBit(Bit.WIZ_CALLBACK);
}
/** Returns whether JSDoc is annotated with {@code @polymerBehavior} annotation. */
public boolean isPolymerBehavior() {
return checkBit(Bit.POLYMER_BEHAVIOR);
}
/** Returns whether JSDoc is annotated with {@code @polymer} annotation. */
public boolean isPolymer() {
return checkBit(Bit.POLYMER);
}
/** Returns whether JSDoc is annotated with {@code @customElement} annotation. */
public boolean isCustomElement() {
return checkBit(Bit.CUSTOM_ELEMENT);
}
/** Returns whether JSDoc is annotated with {@code @mixinClass} annotation. */
public boolean isMixinClass() {
return checkBit(Bit.MIXIN_CLASS);
}
/** Returns whether JSDoc is annotated with {@code @mixinFunction} annotation. */
public boolean isMixinFunction() {
return checkBit(Bit.MIXIN_FUNCTION);
}
/** Gets the description specified by the {@code @license} annotation. */
public String getLicense() {
return LICENSE.get(this);
}
@Override
public String toString() {
return toStringVerbose();
}
@VisibleForTesting
public String toStringVerbose() {
MoreObjects.ToStringHelper helper =
MoreObjects.toStringHelper(this)
.add("bitset", (propertyBits == 0) ? null : Long.toHexString(propertyBits));
for (Bit b : Bit.values()) {
if (checkBit(b)) {
helper.add("bit:" + b.name, true);
}
}
long bits = propertyKeysBitset;
int index = 0;
while (bits > 0) {
int low = Long.numberOfTrailingZeros(bits);
bits &= ~(1L << low);
helper = helper.add(Property.values[low].name, getPropertyValueByIndex(index++));
}
return helper.omitNullValues().toString();
}
/** Returns whether this {@link JSDocInfo} contains a type for {@code @extends} annotation. */
public boolean hasBaseType() {
return getBaseType() != null;
}
/**
* 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() {
return IMPLEMENTED_INTERFACES.getUnmodifiable(this);
}
/** Gets the number of interfaces specified by the {@code @implements} annotation. */
public int getImplementedInterfaceCount() {
ArrayList> list = IMPLEMENTED_INTERFACES.get(this);
return list != null ? list.size() : 0;
}
/**
* Returns the interfaces extended by an interface
*
* @return An immutable list of JSTypeExpression objects that can be resolved to types.
*/
public List getExtendedInterfaces() {
return EXTENDED_INTERFACES.getUnmodifiable(this);
}
/** Gets the number of extended interfaces specified */
public int getExtendedInterfacesCount() {
ArrayList> list = EXTENDED_INTERFACES.get(this);
return list != null ? list.size() : 0;
}
/** Returns the deprecation reason or null if none specified. */
public String getDeprecationReason() {
return DEPRECATION_REASON.get(this);
}
/** Returns the set of suppressed warnings. */
public Set getSuppressions() {
ImmutableMap, String> suppressions = SUPPRESSIONS.get(this);
if (suppressions == null) {
return ImmutableSet.of();
}
Set> nameSets = suppressions.keySet();
Set names = new LinkedHashSet<>();
for (Set nameSet : nameSets) {
names.addAll(nameSet);
}
return ImmutableSet.copyOf(names);
}
/**
* Returns a map containing key=set of suppressions, and value=the corresponding description for
* the set.
*/
public ImmutableMap, String> getSuppressionsAndTheirDescription() {
ImmutableMap, String> suppressions = SUPPRESSIONS.get(this);
return suppressions != null ? suppressions : ImmutableMap.of();
}
/** Returns the set of sideeffect notations. */
public Set getModifies() {
ImmutableSet modifies = MODIFIES.get(this);
return modifies != null ? modifies : ImmutableSet.of();
}
/** Returns whether a description exists for the parameter with the specified name. */
public boolean hasDescriptionForParameter(String name) {
LinkedHashMap params = PARAMETER_DESCRIPTIONS.get(this);
return params != null && params.containsKey(name);
}
/** Returns the description for the parameter with the given name, if its exists. */
public String getDescriptionForParameter(String name) {
LinkedHashMap params = PARAMETER_DESCRIPTIONS.get(this);
return params != null ? params.get(name) : null;
}
/** Returns the list of authors or null if none. */
public List getAuthors() {
return AUTHORS.get(this);
}
/** Returns the list of references or null if none. */
public List getReferences() {
return SEES.get(this);
}
/** Returns the description of the returned object or null if none specified. */
public String getReturnDescription() {
return RETURN_DESCRIPTION.get(this);
}
/** Returns the block-level description or null if none specified. */
public String getBlockDescription() {
return BLOCK_DESCRIPTION.get(this);
}
/** Returns whether this has a fileoverview flag. */
public boolean hasFileOverview() {
return checkBit(Bit.FILEOVERVIEW);
}
/** Returns the file overview or null if none specified. */
public String getFileOverview() {
return FILEOVERVIEW_DESCRIPTION.get(this);
}
/** Returns whether this has an enhanced namespace. */
public boolean hasEnhance() {
return checkBit(Bit.ENHANCED_NAMESPACE);
}
/** Returns the enhanced namespace or null if none is specified. */
public String getEnhance() {
return ENHANCED_NAMESPACE.get(this);
}
/** Gets the list of all markers for the documentation in this JSDoc. */
public Collection getMarkers() {
ArrayList markers = MARKERS.get(this);
return markers != null ? markers : ImmutableList.of();
}
/**
* Gets the @template type names.
*
* Excludes @template types from TTL; get those with {@link #getTypeTransformations()}
*/
public ImmutableList getTemplateTypeNames() {
LinkedHashMap map = TEMPLATE_TYPE_NAMES.get(this);
return map != null ? ImmutableList.copyOf(map.keySet()) : ImmutableList.of();
}
public ImmutableMap getTemplateTypes() {
LinkedHashMap map = TEMPLATE_TYPE_NAMES.get(this);
return map != null ? ImmutableMap.copyOf(map) : ImmutableMap.of();
}
/** Gets the type transformations. */
public ImmutableMap getTypeTransformations() {
LinkedHashMap map = TYPE_TRANSFORMATIONS.get(this);
return map != null ? ImmutableMap.copyOf(map) : ImmutableMap.of();
}
/**
* 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
*/
@SuppressWarnings("unchecked")
public Collection getTypeExpressions() {
ImmutableList.Builder builder = ImmutableList.builder();
long bits = propertyKeysBitset;
int index = 0;
while (bits > 0) {
int low = Long.numberOfTrailingZeros(bits);
bits &= ~(1L << low);
Property prop = (Property) Property.values[low];
for (JSTypeExpression type : prop.getTypeExpressions(getPropertyValueByIndex(index++))) {
if (type != null) {
builder.add(type);
}
}
}
return builder.build();
}
/**
* 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 MODIFIES.get(this) != null;
}
/**
* Returns the original JSDoc comment string. Returns null unless parseJsDocDocumentation is
* enabled via the ParserConfig.
*/
public String getOriginalCommentString() {
return SOURCE_COMMENT.get(this);
}
public int getOriginalCommentPosition() {
Integer pos = ORIGINAL_COMMENT_POSITION.get(this);
return pos != null ? pos : 0;
}
/** Get the value of the @modifies{this} annotation stored in the doc info. */
public boolean modifiesThis() {
return getModifies().contains("this");
}
/** Returns 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")));
}
protected Object getPropertyValueByIndex(int index) {
if (propertyValues == null) {
throw new IllegalArgumentException("no property value");
}
if (propertyValues instanceof Object[]) {
return ((Object[]) propertyValues)[index];
}
if (index != 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
return propertyValues;
}
private static Object packPropertyValues(List values) {
return values.isEmpty() ? null : (values.size() == 1 ? values.get(0) : values.toArray());
}
/**
* A builder for {@link JSDocInfo} objects. This builder is required because JSDocInfo instances
* have immutable structure. It provides early incompatibility detection among properties stored
* on the {@code JSDocInfo} object being created.
*/
public static class Builder {
TreeMap, Object> props = new TreeMap<>();
long bits = 0L;
// whether the current JSDocInfo has valuable information
boolean populated;
// the current marker, if any.
Marker currentMarker;
// the set of unique license texts
Set licenseTexts;
public static Builder copyFrom(JSDocInfo info) {
return info.toBuilder();
}
public static Builder maybeCopyFrom(@Nullable JSDocInfo info) {
return info != null ? info.toBuilder() : JSDocInfo.builder().parseDocumentation();
}
/**
* Returns a JSDocInfo.Builder that contains a copy of the given JSDocInfo in which only the
* {@code @type} field of the JSDocInfo is replaced with the given typeExpression. This is done
* to prevent generating code in the client module which references local variables from another
* module.
*/
public static Builder maybeCopyFromWithNewType(
JSDocInfo info, JSTypeExpression typeExpression) {
if (info == null) {
return JSDocInfo.builder().parseDocumentation().setType(typeExpression);
}
return info.toBuilder().setType(typeExpression);
}
public static Builder copyFromWithNewType(JSDocInfo info, JSTypeExpression typeExpression) {
return info.toBuilder().setType(typeExpression);
}
/**
* Returns a JSDocInfo.Builder that contains a JSDoc in which all module local types (which may
* be inside {@code @param}, {@code @type} or {@code @returns} are replaced with unknown. This
* is done to prevent generating code in the client module which references local variables from
* another module.
*/
public static Builder maybeCopyFromAndReplaceNames(
JSDocInfo info, Set moduleLocalNamesToReplace) {
return info != null
? copyFromAndReplaceNames(info, moduleLocalNamesToReplace)
: JSDocInfo.builder().parseDocumentation();
}
private static Builder copyFromAndReplaceNames(JSDocInfo info, Set oldNames) {
return info.cloneAndReplaceTypeNames(oldNames).toBuilder(); // TODO - populated
}
/**
* Configures the builder to parse documentation. This should be called immediately after
* instantiating the builder if documentation should be included, since it enables various
* operations to do work that would otherwise be no-ops.
*/
public Builder parseDocumentation() {
setBit(Bit.INCLUDE_DOCUMENTATION, true);
return this;
}
public boolean shouldParseDocumentation() {
return checkBit(Bit.INCLUDE_DOCUMENTATION);
}
/**
* Sets the original JSDoc comment string. This is a no-op if the builder isn't configured to
* record documentation.
*/
public void recordOriginalCommentString(String sourceComment) {
if (shouldParseDocumentation()) {
populated = true;
setProp(SOURCE_COMMENT, sourceComment);
}
}
/** Sets the position of original JSDoc comment. */
public void recordOriginalCommentPosition(int position) {
if (shouldParseDocumentation()) {
populated = true;
setProp(ORIGINAL_COMMENT_POSITION, position);
}
}
/**
* Returns whether this builder is populated with information that can be used to {@link #build}
* a {@link JSDocInfo} object that has a fileoverview tag.
*/
public boolean isPopulatedWithFileOverview() {
return populated
&& (bits
& (Bit.FILEOVERVIEW.mask
| Bit.EXTERNS.mask
| Bit.NOCOMPILE.mask
| Bit.TYPE_SUMMARY.mask
| Bit.ENHANCED_NAMESPACE.mask))
!= 0;
}
/** Returns whether this builder recorded a description. */
public boolean isDescriptionRecorded() {
return props.get(DESCRIPTION) != null;
}
/**
* Builds a {@link JSDocInfo} object based on the populated information and returns it.
*
* @return a {@link JSDocInfo} object populated with the values given to this builder. If no
* value was populated, this method simply returns {@code null}
*/
public JSDocInfo build() {
return build(/* always= */ false);
}
/**
* Builds a {@link JSDocInfo} object based on the populated information and returns it.
*
* @param always Return an default JSDoc object.
* @return a {@link JSDocInfo} object populated with the values given to this builder. If no
* value was populated and {@code always} is false, returns {@code null}. If {@code always}
* is true, returns a default JSDocInfo.
*/
public JSDocInfo build(boolean always) {
if (populated || always) {
JSDocInfo info = new JSDocInfo(bits, props);
populated = false;
return info;
}
return null;
}
/**
* Builds a {@link JSDocInfo} object based on the populated information and returns it. Once
* this method is called, the builder can be reused to build another {@link JSDocInfo} object.
*
* @return a {@link JSDocInfo} object populated with the values given to this builder. If no
* value was populated, this method simply returns {@code null}
*/
public JSDocInfo buildAndReset() {
JSDocInfo info = build();
bits &= Bit.INCLUDE_DOCUMENTATION.mask; // only keep this one flag
props.clear();
populated = false;
return info;
}
/**
* Adds a marker to the current JSDocInfo and populates the marker with the annotation
* information.
*/
public void markAnnotation(String annotation, int lineno, int charno) {
Marker marker = addMarker();
if (marker != null) {
TrimmedStringPosition position = new TrimmedStringPosition();
position.setItem(annotation);
position.setPositionInformation(lineno, charno, lineno, charno + annotation.length());
marker.setAnnotation(position);
populated = true;
}
currentMarker = marker;
}
private Marker addMarker() {
if (shouldParseDocumentation()) {
ArrayList markers = getProp(MARKERS);
if (markers == null) {
setProp(MARKERS, markers = new ArrayList<>());
}
Marker marker = new Marker();
markers.add(marker);
return marker;
}
return null;
}
/** Adds a textual block to the current marker. */
public void markText(
String text, int startLineno, int startCharno, int endLineno, int endCharno) {
if (currentMarker != null) {
StringPosition position = new StringPosition();
position.setItem(text);
position.setPositionInformation(startLineno, startCharno, endLineno, endCharno);
currentMarker.setDescription(position);
}
}
/** Adds a type declaration to the current marker. */
public void markTypeNode(
Node typeNode, int lineno, int startCharno, int endLineno, int endCharno, boolean hasLC) {
if (currentMarker != null) {
TypePosition position = new TypePosition();
position.setItem(typeNode);
position.setHasBrackets(hasLC);
position.setPositionInformation(lineno, startCharno, endLineno, endCharno);
currentMarker.setType(position);
}
}
/** Adds a name declaration to the current marker. */
public void markName(String name, Node templateNode, int lineno, int charno) {
if (currentMarker != null) {
// Record the name as both a SourcePosition and a
// SourcePosition. The form is deprecated,
// because is more consistent with how other name
// references are handled (see #markTypeNode)
//
// TODO(nicksantos): Remove all uses of the Name position
// and replace them with the NameNode position.
TrimmedStringPosition position = new TrimmedStringPosition();
position.setItem(name);
position.setPositionInformation(lineno, charno, lineno, charno + name.length());
NamePosition nodePos = new NamePosition();
Node node = Node.newString(Token.NAME, name).setLinenoCharno(lineno, charno);
node.setLength(name.length());
if (templateNode != null) {
node.setStaticSourceFileFrom(templateNode);
}
nodePos.setItem(node);
nodePos.setPositionInformation(lineno, charno, lineno, charno + name.length());
currentMarker.setNameNode(nodePos);
}
}
/**
* Records a block-level description.
*
* @return {@code true} if the description was recorded.
*/
public boolean recordBlockDescription(String description) {
populated = true;
if (!shouldParseDocumentation()) {
return true;
}
return populateProp(BLOCK_DESCRIPTION, description);
}
/**
* Records a visibility.
*
* @return {@code true} if the visibility was recorded and {@code false} if it was already
* defined
*/
public boolean recordVisibility(Visibility visibility) {
if (getProp(VISIBILITY) == null) {
populated = true;
setProp(VISIBILITY, visibility);
return true;
}
return false;
}
public void overwriteVisibility(Visibility visibility) {
populated = true;
setProp(VISIBILITY, visibility);
}
/**
* Records a typed parameter.
*
* @return {@code true} if the typed parameter was recorded and {@code false} if a parameter
* with the same name was already defined
*/
public boolean recordParameter(String parameterName, JSTypeExpression type) {
return !hasAnySingletonTypeTags()
&& populatePropEntry(PARAMETERS, RhinoStringPool.addOrGet(parameterName), type);
}
/**
* Records a parameter's description.
*
* @return {@code true} if the parameter's description was recorded and {@code false} if a
* parameter with the same name was already defined
*/
public boolean recordParameterDescription(String parameterName, String description) {
if (!shouldParseDocumentation()) {
return true;
}
return populatePropEntry(
PARAMETER_DESCRIPTIONS, RhinoStringPool.addOrGet(parameterName), description);
}
/**
* Records a template type name.
*
* @return {@code true} if the template type name was recorded and {@code false} if the input
* template type name was already defined.
*/
public boolean recordTemplateTypeName(String name) {
return recordTemplateTypeName(name, null);
}
public boolean recordTemplateTypeName(String name, JSTypeExpression bound) {
if (bound == null) {
bound = JSTypeExpression.IMPLICIT_TEMPLATE_BOUND;
}
Map transformations = getProp(TYPE_TRANSFORMATIONS);
if ((transformations != null && transformations.containsKey(name))
|| props.containsKey(TYPEDEF_TYPE)) {
return false;
}
return populatePropEntry(TEMPLATE_TYPE_NAMES, RhinoStringPool.addOrGet(name), bound);
}
/** Records a type transformation expression together with its template type name. */
public boolean recordTypeTransformation(String name, Node expr) {
Map names = getProp(TEMPLATE_TYPE_NAMES);
if (names != null && names.containsKey(name)) {
return false;
}
return populatePropEntry(TYPE_TRANSFORMATIONS, RhinoStringPool.addOrGet(name), expr);
}
/**
* Records a throw annotation description.
*
* @return {@code true} if the type's description was recorded. The description of a throw
* annotation is the text including the type.
*/
public boolean recordThrowsAnnotation(String annotation) {
populated = true;
// TODO(user): Does it make sense to check for singleton tags here?
// Note that if the @throws annotation appears before a singleton tag like @type,
// the throws annotation is preserved, but if it appears after the singleton tag,
// it gets dropped.
if (!hasAnySingletonTypeTags()) {
List throwsAnnotations = getPropWithDefault(THROWS_ANNOTATIONS, ArrayList::new);
if (shouldParseDocumentation()) {
throwsAnnotations.add(annotation);
} else if (throwsAnnotations.isEmpty()) {
// Add at least one annotation so that PureFunctionIdentifier knows about
// the side effect.
throwsAnnotations.add("");
}
return true;
}
return false;
}
/** Adds an author to the current information. */
public boolean recordAuthor(String author) {
populated = true;
if (shouldParseDocumentation()) {
getPropWithDefault(AUTHORS, ArrayList::new).add(author);
}
// NOTE: this could be removed, since it's always true.
return true;
}
/** Adds a reference ("@see") to the current information. */
public boolean recordReference(String reference) {
populated = true;
if (shouldParseDocumentation()) {
getPropWithDefault(SEES, ArrayList::new).add(reference);
}
// NOTE: this could be removed, since it's always true.
return true;
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isConsistentIdGenerator()} flag set to {@code true}.
*
* @return {@code true} if the consistentIdGenerator flag was recorded and {@code false} if it
* was already recorded
*/
public boolean recordConsistentIdGenerator() {
return populateProp(ID_GENERATOR, IdGenerator.CONSISTENT);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isStableIdGenerator()} flag set to {@code true}.
*
* @return {@code true} if the stableIdGenerator flag was recorded and {@code false} if it was
* already recorded.
*/
public boolean recordStableIdGenerator() {
return populateProp(ID_GENERATOR, IdGenerator.STABLE);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isXidGenerator()} flag set to {@code true}.
*
* @return {@code true} if the isXidGenerator flag was recorded and {@code false} if it was
* already recorded.
*/
public boolean recordXidGenerator() {
return populateProp(ID_GENERATOR, IdGenerator.XID);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isStableIdGenerator()} flag set to {@code true}.
*
* @return {@code true} if the stableIdGenerator flag was recorded and {@code false} if it was
* already recorded.
*/
public boolean recordMappedIdGenerator() {
return populateProp(ID_GENERATOR, IdGenerator.MAPPED);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isIdGenerator()} flag set to {@code true}.
*
* @return {@code true} if the idGenerator flag was recorded and {@code false} if it was already
* recorded
*/
public boolean recordIdGenerator() {
return populateProp(ID_GENERATOR, IdGenerator.UNIQUE);
}
/** Records the deprecation reason. */
public boolean recordDeprecationReason(String reason) {
return populateProp(DEPRECATION_REASON, reason);
}
/** Returns whether a deprecation reason has been recorded. */
public boolean isDeprecationReasonRecorded() {
return getProp(DEPRECATION_REASON) != null;
}
public void recordSuppressions(ImmutableSet suppressions, String description) {
populated = true;
ImmutableMap.Builder, String> mapBuilder = ImmutableMap.builder();
ImmutableMap, String> current = getProp(SUPPRESSIONS);
if (current != null) {
if (current.containsKey(suppressions)) {
// Exact @suppress warning exists already. Return without recording.
return;
}
mapBuilder.putAll(current);
}
mapBuilder.put(internSet(suppressions), description);
ImmutableMap, String> suppressionsMap = mapBuilder.buildOrThrow();
setProp(SUPPRESSIONS, suppressionsMap);
}
/**
* Records the list of suppressed warnings, possibly adding to the set of already configured
* warnings.
*/
public void recordSuppressions(Set suppressions) {
recordSuppressions(ImmutableSet.copyOf(suppressions), "");
}
public void recordSuppression(String suppression) {
recordSuppressions(ImmutableSet.of(suppression), "");
}
/** Records the list of modifies warnings. */
public boolean recordModifies(Set modifies) {
return !hasAnySingletonSideEffectTags() && populateProp(MODIFIES, internSet(modifies));
}
/**
* Records a type.
*
* @return {@code true} if the type was recorded and {@code false} if it is invalid or was
* already defined
*/
public boolean recordType(JSTypeExpression type) {
return type != null && !hasAnyTypeRelatedTags() && populateProp(TYPE, type);
}
public void recordInlineType() {
populateBit(Bit.INLINE_TYPE, true);
}
/**
* Records that the {@link JSDocInfo} being built should be populated with a {@code typedef}'d
* type.
*/
public boolean recordTypedef(JSTypeExpression type) {
return type != null
&& !hasAnyTypeRelatedTags()
&& getProp(TEMPLATE_TYPE_NAMES) == null
&& populateProp(TYPEDEF_TYPE, type);
}
/**
* Records a return type.
*
* @return {@code true} if the return type was recorded and {@code false} if it is invalid or
* was already defined
*/
public boolean recordReturnType(JSTypeExpression type) {
return type != null && !hasAnySingletonTypeTags() && populateProp(RETURN_TYPE, type);
}
/**
* Records a return description
*
* @return {@code true} if the return description was recorded and {@code false} if it is
* invalid or was already defined
*/
public boolean recordReturnDescription(String description) {
if (!shouldParseDocumentation()) {
return true;
}
return populateProp(RETURN_DESCRIPTION, description);
}
/**
* Records the type of a define.
*
* 'Define' values are special constants that may be manipulated by the compiler. They are
* designed to mimic the #define command in the C preprocessor.
*/
public boolean recordDefineType(JSTypeExpression type) {
if (type != null && !checkBit(Bit.CONST) && !checkBit(Bit.DEFINE) && recordType(type)) {
return populateBit(Bit.DEFINE, true);
}
return false;
}
/**
* Records a parameter type to an enum.
*
* @return {@code true} if the enum's parameter type was recorded and {@code false} if it was
* invalid or already defined
*/
public boolean recordEnumParameterType(JSTypeExpression type) {
if (type != null && !hasAnyTypeRelatedTags()) {
setProp(ENUM_PARAMETER_TYPE, type);
populated = true;
return true;
}
return false;
}
// TODO(tbreisacher): Disallow nullable types here. If someone writes
// "@this {Foo}" in their JS we automatically treat it as though they'd written
// "@this {!Foo}". But, if the type node is created in the compiler
// (e.g. in the WizPass) we should explicitly add the '!'
/**
* Records a type for {@code @this} annotation.
*
* @return {@code true} if the type was recorded and {@code false} if it is invalid or if it
* collided with {@code @enum} or {@code @type} annotations
*/
public boolean recordThisType(JSTypeExpression type) {
return type != null && !hasAnySingletonTypeTags() && populateProp(THIS_TYPE, type);
}
/**
* Records a base type.
*
* @return {@code true} if the base type was recorded and {@code false} if it was already
* defined
*/
public boolean recordBaseType(JSTypeExpression type) {
return type != null && !hasAnySingletonTypeTags() && populateProp(BASE_TYPE, type);
}
/**
* Changes a base type, even if one has already been set on currentInfo.
*
* @return {@code true} if the base type was changed successfully.
*/
public boolean changeBaseType(JSTypeExpression type) {
if (type != null && !hasAnySingletonTypeTags()) {
setProp(BASE_TYPE, type);
populated = true;
return true;
}
return false;
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link JSDocInfo#isConstant()}
* flag set to {@code true}.
*
* @return {@code true} if the constancy was recorded and {@code false} if it was already
* defined
*/
public boolean recordConstancy() {
return populateBit(Bit.CONST, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link JSDocInfo#isConstant()}
* flag set to {@code false}.
*
* @return {@code true} if the mutability was recorded and {@code false} if it was already
* defined
*/
public boolean recordMutable() {
return populateBit(Bit.CONST, false);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link JSDocInfo#isFinal()}
* flag set to {@code true}.
*
* @return {@code true} if the finality was recorded and {@code false} if it was already defined
*/
public boolean recordFinality() {
return populateBit(Bit.FINAL, true);
}
/**
* Records a description giving context for translation (i18n).
*
* @return {@code true} if the description was recorded and {@code false} if the description was
* invalid or was already defined
*/
public boolean recordDescription(String description) {
return populateProp(DESCRIPTION, description);
}
/** Records a tsType giving context for .d.ts generation */
public void recordTsType(String tsType) {
populated = true;
getPropWithDefault(TS_TYPES, ArrayList::new).add(tsType);
}
/**
* Records a meaning giving context for translation (i18n). Different meanings will result in
* different translations.
*
* @return {@code true} If the meaning was successfully updated.
*/
public boolean recordMeaning(String meaning) {
return populateProp(MEANING, meaning);
}
/**
* Records an ID for an alternate message to be used if this message is not yet translated.
*
* @return {@code true} If the alternate message ID was successfully updated.
*/
public boolean recordAlternateMessageId(String alternateMessageId) {
return populateProp(ALTERNATE_MESSAGE_ID, alternateMessageId);
}
/**
* Records an identifier for a Closure Primitive. function.
*
* @return {@code true} If the id was successfully updated.
*/
public boolean recordClosurePrimitiveId(String closurePrimitiveId) {
return populateProp(CLOSURE_PRIMITIVE_ID, RhinoStringPool.addOrGet(closurePrimitiveId));
}
/**
* Records a fileoverview description.
*
* @return {@code true} if the description was recorded and {@code false} if the description was
* invalid or was already defined.
*/
public boolean recordFileOverview(String description) {
setBit(Bit.FILEOVERVIEW, true);
populated = true;
if (!shouldParseDocumentation()) {
return true;
}
return populateProp(FILEOVERVIEW_DESCRIPTION, description);
}
/**
* Records enhanced namespace.
*
* @return {@code true} If the enhanced namespace was recorded.
*/
public boolean recordEnhance(String namespace) {
setBit(Bit.ENHANCED_NAMESPACE, true);
populated = true;
return populateProp(ENHANCED_NAMESPACE, namespace);
}
public boolean recordLicense(String license) {
setProp(LICENSE, RhinoStringPool.addOrGet(license));
populated = true;
return true;
}
public boolean addLicense(String license) {
// The vast majority of JSDoc doesn't have @license so it make sense to be lazy about building
// the HashSet.
if (licenseTexts == null) {
// The HashSet is only used to remove duplicates, it is never read beyond the add,
// so LinkedHashSet is not required.
licenseTexts = new HashSet<>();
}
if (!licenseTexts.add(license)) {
return false;
}
String txt = getProp(LICENSE);
return recordLicense(RhinoStringPool.addOrGet(nullToEmpty(txt) + license));
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link JSDocInfo#isHidden()}
* flag set to {@code true}.
*
* @return {@code true} if the hiddenness was recorded and {@code false} if it was already
* defined
*/
public boolean recordHiddenness() {
return populateBit(Bit.HIDDEN, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isNoCompile()} flag set to {@code true}.
*
* @return {@code true} if the no compile flag was recorded and {@code false} if it was already
* recorded
*/
public boolean recordNoCompile() {
return populateBit(Bit.NOCOMPILE, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isNoCollapse()} flag set to {@code true}.
*
* @return {@code true} if the no collapse flag was recorded and {@code false} if it was already
* recorded
*/
public boolean recordNoCollapse() {
return populateBit(Bit.NOCOLLAPSE, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link JSDocInfo#isNoInline()}
* flag set to {@code true}.
*
* @return {@code true} if the no inline flag was recorded and {@code false} if it was already
* recorded
*/
public boolean recordNoInline() {
return populateBit(Bit.NOINLINE, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isPureOrBreakMyCode()} flag set to {@code true}.
*
* @return {@code true} if the no pureOrBreakMyCode flag was recorded and {@code false} if it
* was already recorded
*/
public boolean recordPureOrBreakMyCode() {
return populateBit(Bit.PURE_OR_BREAK_MY_CODE, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isCollapsibleOrBreakMyCode()} flag set to {@code true}.
*
* @return {@code true} if the no collapsibleOrBreakMyCode flag was recorded and {@code false}
* if it was already recorded
*/
public boolean recordCollapsibleOrBreakMyCode() {
return populateBit(Bit.COLLAPSIBLE_OR_BREAK_MY_CODE, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isConstructor()} flag set to {@code true}.
*
* @return {@code true} if the constructor was recorded and {@code false} if it was already
* defined or it was incompatible with the existing flags
*/
public boolean recordConstructor() {
return !hasAnySingletonTypeTags()
&& !isConstructorOrInterface()
&& populateBit(Bit.CONSTRUCTOR, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#usesImplicitMatch()} flag set to {@code true}.
*
* @return {@code true} if the {@code @record} tag was recorded and {@code false} if it was
* already defined or it was incompatible with the existing flags
*/
public boolean recordImplicitMatch() {
return !hasAnySingletonTypeTags()
&& !isConstructorOrInterface()
&& populateBit(Bit.RECORD, true)
&& populateBit(Bit.INTERFACE, true);
}
public boolean recordProvideGoog() {
return populateBit(Bit.PROVIDE_GOOG, true);
}
public boolean recordProvideAlreadyProvided() {
return populateBit(Bit.PROVIDE_ALREADY_PROVIDED, true);
}
/**
* Whether the {@link JSDocInfo} being built will have its {@link JSDocInfo#isConstructor()}
* flag set to {@code true}.
*/
public boolean isConstructorRecorded() {
return checkBit(Bit.CONSTRUCTOR);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#makesUnrestricted()} flag set to {@code true}.
*
* @return {@code true} if annotation was recorded and {@code false} if it was already defined
* or it was incompatible with the existing flags
*/
public boolean recordUnrestricted() {
return !hasAnySingletonTypeTags()
&& ((bits & (Bit.INTERFACE.mask | Bit.DICT.mask | Bit.STRUCT.mask)) == 0)
&& populateBit(Bit.UNRESTRICTED, true);
}
public boolean isUnrestrictedRecorded() {
return checkBit(Bit.UNRESTRICTED);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link JSDocInfo#isAbstract()}
* flag set to {@code true}.
*
* @return {@code true} if the flag was recorded and {@code false} if it was already defined or
* it was incompatible with the existing flags
*/
public boolean recordAbstract() {
return !hasAnySingletonTypeTags()
&& ((bits & (Bit.INTERFACE.mask | Bit.FINAL.mask)) == 0)
&& getProp(VISIBILITY) != Visibility.PRIVATE
&& populateBit(Bit.ABSTRACT, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#makesStructs()} flag set to {@code true}.
*
* @return {@code true} if the struct was recorded and {@code false} if it was already defined
* or it was incompatible with the existing flags
*/
public boolean recordStruct() {
return !hasAnySingletonTypeTags()
&& ((bits & (Bit.DICT.mask | Bit.UNRESTRICTED.mask)) == 0)
&& populateBit(Bit.STRUCT, true);
}
public boolean isStructRecorded() {
return checkBit(Bit.STRUCT);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link JSDocInfo#makesDicts()}
* flag set to {@code true}.
*
* @return {@code true} if the dict was recorded and {@code false} if it was already defined or
* it was incompatible with the existing flags
*/
public boolean recordDict() {
return !hasAnySingletonTypeTags()
&& ((bits & (Bit.STRUCT.mask | Bit.UNRESTRICTED.mask)) == 0)
&& populateBit(Bit.DICT, true);
}
public boolean isDictRecorded() {
return checkBit(Bit.DICT);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link JSDocInfo#isOverride()}
* flag set to {@code true}.
*/
public boolean recordOverride() {
return populateBit(Bit.OVERRIDE, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isDeprecated()} flag set to {@code true}.
*/
public boolean recordDeprecated() {
return populateBit(Bit.DEPRECATED, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isInterface()} flag set to {@code true}.
*
* @return {@code true} if the flag was recorded and {@code false} if it was already defined or
* it was incompatible with the existing flags
*/
public boolean recordInterface() {
return !hasAnySingletonTypeTags()
&& ((bits & (Bit.CONSTRUCTOR.mask | Bit.ABSTRACT.mask)) == 0)
&& populateBit(Bit.INTERFACE, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link JSDocInfo#isExport()}
* flag set to {@code true}.
*/
public boolean recordExport() {
return populateBit(Bit.EXPORT, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link JSDocInfo#isExport()}
* flag set to {@code false}.
*/
public boolean removeExport() {
return populateBit(Bit.EXPORT, false);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isImplicitCast()} flag set to {@code true}.
*/
public boolean recordImplicitCast() {
return populateBit(Bit.IMPLICITCAST, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isNoSideEffects()} flag set to {@code true}.
*/
public boolean recordNoSideEffects() {
return !hasAnySingletonSideEffectTags() && populateBit(Bit.NOSIDEEFFECTS, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link JSDocInfo#isExterns()}
* flag set to {@code true}.
*/
public boolean recordExterns() {
return populateBit(Bit.EXTERNS, true);
}
/**
* Records that the {@link JSDocInfo} being built should have its {@link
* JSDocInfo#isTypeSummary()} flag set to {@code true}.
*/
public boolean recordTypeSummary() {
return populateBit(Bit.TYPE_SUMMARY, true);
}
/**
* Whether the {@link JSDocInfo} being built will have its {@link JSDocInfo#isInterface()} flag
* set to {@code true}.
*/
public boolean isInterfaceRecorded() {
return checkBit(Bit.INTERFACE);
}
/** Returns whether a parameter of the given name has already been recorded. */
public boolean hasParameter(String name) {
Map params = getProp(PARAMETERS);
return params != null && params.containsKey(name);
}
/** Records an implemented interface. */
public boolean recordImplementedInterface(JSTypeExpression interfaceType) {
return interfaceType != null && addUnique(IMPLEMENTED_INTERFACES, interfaceType);
}
/** Records an extended interface type. */
public boolean recordExtendedInterface(JSTypeExpression interfaceType) {
return interfaceType != null && addUnique(EXTENDED_INTERFACES, interfaceType);
}
private boolean addUnique(Property> prop, JSTypeExpression elem) {
ArrayList list = getPropWithDefault(prop, ArrayList::new);
if (list.stream().anyMatch(elem::isEquivalentTo)) {
return false;
} else {
list.add(elem);
populated = true;
return true;
}
}
/** Records that we're lending to another name. */
public boolean recordLends(JSTypeExpression name) {
return !hasAnyTypeRelatedTags() && populateProp(LENDS_NAME, name);
}
/** Returns whether current JSDoc is annotated with {@code @ngInject}. */
public boolean isNgInjectRecorded() {
return checkBit(Bit.NG_INJECT);
}
/** Records that we'd like to add {@code $inject} property inferred from parameters. */
public boolean recordNgInject(boolean ngInject) {
return populateBit(Bit.NG_INJECT, true);
}
/** Returns whether current JSDoc is annotated with {@code @wizaction}. */
public boolean isWizactionRecorded() {
return checkBit(Bit.WIZ_ACTION);
}
/** Records that this method is to be exposed as a wizaction. */
public boolean recordWizaction() {
return populateBit(Bit.WIZ_ACTION, true);
}
/** Returns if current JSDoc is annotated with {@code @wizcallback}. */
public boolean isWizcallbackRecorded() {
return checkBit(Bit.WIZ_CALLBACK);
}
/** Records that this method is to be exposed as a wizcallback. */
public boolean recordWizcallback() {
return populateBit(Bit.WIZ_CALLBACK, true);
}
/** Returns whether current JSDoc is annotated with {@code @polymerBehavior}. */
public boolean isPolymerBehaviorRecorded() {
return checkBit(Bit.POLYMER_BEHAVIOR);
}
/** Records that this method is to be exposed as a polymerBehavior. */
public boolean recordPolymerBehavior() {
return populateBit(Bit.POLYMER_BEHAVIOR, true);
}
/** Returns whether current JSDoc is annotated with {@code @polymer}. */
public boolean isPolymerRecorded() {
return checkBit(Bit.POLYMER);
}
/** Records that this method is to be exposed as a polymer element. */
public boolean recordPolymer() {
return populateBit(Bit.POLYMER, true);
}
/** Returns whether current JSDoc is annotated with {@code @customElement}. */
public boolean isCustomElementRecorded() {
return checkBit(Bit.CUSTOM_ELEMENT);
}
/** Records that this method is to be exposed as a customElement. */
public boolean recordCustomElement() {
return populateBit(Bit.CUSTOM_ELEMENT, true);
}
/** Returns whether current JSDoc is annotated with {@code @mixinClass}. */
public boolean isMixinClassRecorded() {
return checkBit(Bit.MIXIN_CLASS);
}
/** Records that this method is to be exposed as a mixinClass. */
public boolean recordMixinClass() {
return populateBit(Bit.MIXIN_CLASS, true);
}
/** Returns whether current JSDoc is annotated with {@code @mixinFunction}. */
public boolean isMixinFunctionRecorded() {
return checkBit(Bit.MIXIN_FUNCTION);
}
/** Records that this method is to be exposed as a mixinFunction. */
public boolean recordMixinFunction() {
return populateBit(Bit.MIXIN_FUNCTION, true);
}
// TODO(sdh): this is a new method - consider removing it in favor of recordType?
// The main difference is that this force-sets the type, while recordType backs off.
// This is useful for (e.g.) copyFromWithNewType.
Builder setType(JSTypeExpression type) {
props.remove(RETURN_TYPE);
props.remove(ENUM_PARAMETER_TYPE);
props.remove(TYPEDEF_TYPE);
setProp(TYPE, type);
return this;
}
/**
* Whether the current doc info has other type tags, like {@code @param} or {@code @return} or
* {@code @type} or etc.
*/
private boolean hasAnyTypeRelatedTags() {
return (bits & (Bit.CONSTRUCTOR.mask | Bit.INTERFACE.mask | Bit.ABSTRACT.mask)) != 0
|| hasAnyParameters()
|| getProp(RETURN_TYPE) != null
|| getProp(BASE_TYPE) != null
|| !isPropEmpty(EXTENDED_INTERFACES)
|| getProp(LENDS_NAME) != null
|| getProp(THIS_TYPE) != null
|| hasAnySingletonTypeTags();
}
private boolean hasAnyParameters() {
Map, ?> params = getProp(PARAMETERS);
return params != null && !params.isEmpty();
}
private boolean isPropEmpty(Property extends Collection>> prop) {
Collection> c = getProp(prop);
return c == null || c.isEmpty();
}
/**
* Whether the current doc info has any of the singleton type tags that may not appear with
* other type tags, like {@code @type} or {@code @typedef}.
*/
private boolean hasAnySingletonTypeTags() {
return getProp(TYPE) != null
|| getProp(TYPEDEF_TYPE) != null
|| getProp(ENUM_PARAMETER_TYPE) != null;
}
/**
* Whether the current doc info has any of the singleton type tags that may not appear with
* other type tags, like {@code @type} or {@code @typedef}.
*/
private boolean hasAnySingletonSideEffectTags() {
return checkBit(Bit.NOSIDEEFFECTS) || !isPropEmpty(MODIFIES);
}
private boolean isConstructorOrInterface() {
return (bits & (Bit.CONSTRUCTOR.mask | Bit.INTERFACE.mask)) != 0;
}
private void setProp(Property prop, T value) {
props.put(prop, value);
}
@Nullable
@SuppressWarnings("unchecked")
private T getProp(Property prop) {
return (T) props.get(prop);
}
private T getPropWithDefault(Property prop, Supplier supplier) {
T value = getProp(prop);
if (value == null) {
setProp(prop, value = supplier.get());
}
return value;
}
private boolean putPropEntry(Property> prop, K key, V value) {
return getPropWithDefault(prop, LinkedHashMap::new).putIfAbsent(key, value) == null;
}
private boolean populatePropEntry(Property> prop, K key, V value) {
return putPropEntry(prop, key, value) && (populated = true);
}
private boolean populateProp(Property prop, T value) {
populated = true;
return props.putIfAbsent(prop, value) == null;
}
private boolean checkBit(Bit bit) {
return (bits & bit.mask) != 0;
}
private void setBit(Bit bit, boolean value) {
if (value) {
bits |= bit.mask;
} else {
bits &= ~bit.mask;
}
}
private boolean populateBit(Bit bit, boolean value) {
if (checkBit(bit) != value) {
setBit(bit, value);
return populated = true;
}
return false;
}
}
private static ImmutableSet internSet(Set strings) {
ImmutableSet.Builder interned = ImmutableSet.builderWithExpectedSize(strings.size());
for (String s : strings) {
interned.add(RhinoStringPool.addOrGet(s));
}
return interned.build();
}
}