blob: 712a6caf7493b21c4c48092ea616aff986e7e3e0 [file] [log] [blame]
// Copyright 2019 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.skydoc.rendering;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.syntax.FunctionSignature;
import com.google.devtools.build.lib.syntax.Printer;
import com.google.devtools.build.lib.syntax.Printer.BasePrinter;
import com.google.devtools.build.lib.syntax.StarlarkFunction;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.FunctionParamInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.StarlarkFunctionInfo;
import com.google.devtools.skylark.common.DocstringUtils;
import com.google.devtools.skylark.common.DocstringUtils.DocstringInfo;
import com.google.devtools.skylark.common.DocstringUtils.DocstringParseError;
import com.google.devtools.skylark.common.DocstringUtils.ParameterDoc;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/** Contains a number of utility methods for functions and parameters. */
public final class FunctionUtil {
/**
* Create and return a {@link StarlarkFunctionInfo} object encapsulating information obtained from
* the given function and from its parsed docstring.
*
* @param functionName the name of the function in the target scope. (Note this is not necessarily
* the original exported function name; the function may have been renamed in the target
* Starlark file's scope)
* @param fn the function object
* @throws com.google.devtools.build.skydoc.rendering.DocstringParseException if the function's
* docstring is malformed
*/
public static StarlarkFunctionInfo fromNameAndFunction(String functionName, StarlarkFunction fn)
throws DocstringParseException {
String functionDescription = "";
Map<String, String> paramNameToDocMap = Maps.newLinkedHashMap();
String doc = fn.getDocumentation();
if (doc != null) {
List<DocstringParseError> parseErrors = Lists.newArrayList();
DocstringInfo docstringInfo = DocstringUtils.parseDocstring(doc, parseErrors);
if (!parseErrors.isEmpty()) {
throw new DocstringParseException(functionName, fn.getLocation(), parseErrors);
}
functionDescription += docstringInfo.getSummary();
if (!docstringInfo.getSummary().isEmpty() && !docstringInfo.getLongDescription().isEmpty()) {
functionDescription += "\n\n";
}
functionDescription += docstringInfo.getLongDescription();
for (ParameterDoc paramDoc : docstringInfo.getParameters()) {
paramNameToDocMap.put(paramDoc.getParameterName(), paramDoc.getDescription());
}
}
List<FunctionParamInfo> paramsInfo = parameterInfos(fn, paramNameToDocMap);
return StarlarkFunctionInfo.newBuilder()
.setFunctionName(functionName)
.setDocString(functionDescription)
.addAllParameter(paramsInfo)
.build();
}
/** Constructor to be used for normal parameters. */
public static FunctionParamInfo forParam(
String name, String docString, @Nullable Object defaultValue) {
FunctionParamInfo.Builder paramBuilder =
FunctionParamInfo.newBuilder().setName(name).setDocString(docString);
if (defaultValue == null) {
paramBuilder.setMandatory(true);
} else {
BasePrinter printer = Printer.getSimplifiedPrinter();
printer.repr(defaultValue);
String defaultValueString = printer.toString();
if (defaultValueString.isEmpty()) {
defaultValueString = "{unknown object}";
}
paramBuilder.setDefaultValue(defaultValueString).setMandatory(false);
}
return paramBuilder.build();
}
/** Constructor to be used for *args or **kwargs. */
public static FunctionParamInfo forSpecialParam(String name, String docString) {
return FunctionParamInfo.newBuilder()
.setName(name)
.setDocString(docString)
.setMandatory(false)
.build();
}
private static List<FunctionParamInfo> parameterInfos(
StarlarkFunction userDefinedFunction, Map<String, String> paramNameToDocMap) {
FunctionSignature signature = userDefinedFunction.getSignature();
ImmutableList.Builder<FunctionParamInfo> parameterInfos = ImmutableList.builder();
List<String> paramNames = signature.getParameterNames();
int numMandatoryParams = signature.numMandatoryPositionals();
int paramIndex;
// Mandatory parameters.
// Mandatory parameters must always come before optional parameters, so this counts
// down until all mandatory parameters have been exhausted, and then starts filling in
// the optional parameters accordingly.
for (paramIndex = 0; paramIndex < numMandatoryParams; paramIndex++) {
String paramName = paramNames.get(paramIndex);
String paramDoc = paramNameToDocMap.getOrDefault(paramName, "");
parameterInfos.add(forParam(paramName, paramDoc, /*default param*/ null));
}
// Parameters with defaults.
List<Object> defaultValues = userDefinedFunction.getDefaultValues();
if (defaultValues != null) {
for (Object element : defaultValues) {
String paramName = paramNames.get(paramIndex);
String paramDoc = "";
Object defaultParamValue = element;
if (paramNameToDocMap.containsKey(paramName)) {
paramDoc = paramNameToDocMap.get(paramName);
}
parameterInfos.add(forParam(paramName, paramDoc, defaultParamValue));
paramIndex++;
}
}
// *arg
if (signature.hasVarargs()) {
String paramName = paramNames.get(paramIndex);
String paramDoc = "";
if (paramNameToDocMap.containsKey(paramName)) {
paramDoc = paramNameToDocMap.get(paramName);
} else if (paramNameToDocMap.containsKey("*" + paramName)) {
paramDoc = paramNameToDocMap.get("*" + paramName);
}
parameterInfos.add(forSpecialParam(paramName, paramDoc));
paramIndex++;
}
// **kwargs
if (signature.hasKwargs()) {
String paramName = paramNames.get(paramIndex);
String paramDoc = "";
if (paramNameToDocMap.containsKey(paramName)) {
paramDoc = paramNameToDocMap.get(paramName);
} else if (paramNameToDocMap.containsKey("**" + paramName)) {
paramDoc = paramNameToDocMap.get("**" + paramName);
}
parameterInfos.add(forSpecialParam(paramName, paramDoc));
paramIndex++;
}
return parameterInfos.build();
}
}