| // Copyright 2018 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.events.Location; |
| import com.google.devtools.build.lib.syntax.FunctionSignature; |
| import com.google.devtools.build.lib.syntax.SkylarkType; |
| import com.google.devtools.build.lib.syntax.StringLiteral; |
| import com.google.devtools.build.lib.syntax.UserDefinedFunction; |
| 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.Collection; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** Encapsulates information about a user-defined Starlark function. */ |
| public class UserDefinedFunctionInfo { |
| |
| private final String functionName; |
| private final Collection<FunctionParamInfo> parameters; |
| private final String docString; |
| |
| /** |
| * An exception that may be thrown during construction of {@link UserDefinedFunctionInfo} if the |
| * function's docstring is malformed. |
| */ |
| public static class DocstringParseException extends Exception { |
| public DocstringParseException( |
| String functionName, Location definedLocation, List<DocstringParseError> parseErrors) { |
| super(getMessage(functionName, definedLocation, parseErrors)); |
| } |
| |
| private static String getMessage( |
| String functionName, Location definedLocation, List<DocstringParseError> parseErrors) { |
| StringBuilder message = new StringBuilder(); |
| message.append( |
| String.format( |
| "Unable to generate documentation for function %s (defined at %s) " |
| + "due to malformed docstring. Parse errors:\n", |
| functionName, definedLocation)); |
| for (DocstringParseError parseError : parseErrors) { |
| message.append( |
| String.format( |
| " %s line %s: %s\n", |
| definedLocation, |
| parseError.getLineNumber(), |
| parseError.getMessage().replace('\n', ' '))); |
| } |
| return message.toString(); |
| } |
| } |
| |
| /** |
| * Create and return a {@link UserDefinedFunctionInfo} 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 userDefinedFunction the raw function object |
| * @throws DocstringParseException if the function's docstring is malformed |
| */ |
| public static UserDefinedFunctionInfo fromNameAndFunction( |
| String functionName, UserDefinedFunction userDefinedFunction) throws DocstringParseException { |
| String functionDescription = ""; |
| Map<String, String> paramNameToDocMap = Maps.newLinkedHashMap(); |
| |
| StringLiteral docStringLiteral = |
| DocstringUtils.extractDocstring(userDefinedFunction.getStatements()); |
| |
| if (docStringLiteral != null) { |
| List<DocstringParseError> parseErrors = Lists.newArrayList(); |
| DocstringInfo docstringInfo = DocstringUtils.parseDocstring(docStringLiteral, parseErrors); |
| if (!parseErrors.isEmpty()) { |
| throw new DocstringParseException( |
| functionName, userDefinedFunction.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()); |
| } |
| } |
| |
| return new UserDefinedFunctionInfo( |
| functionName, |
| parameterInfos(userDefinedFunction, paramNameToDocMap), |
| functionDescription); |
| } |
| |
| private static List<FunctionParamInfo> parameterInfos( |
| UserDefinedFunction userDefinedFunction, |
| Map<String, String> paramNameToDocMap) { |
| FunctionSignature.WithValues<Object, SkylarkType> signature = |
| userDefinedFunction.getSignature(); |
| ImmutableList.Builder<FunctionParamInfo> parameterInfos = ImmutableList.builder(); |
| |
| List<String> paramNames = signature.getSignature().getNames(); |
| int numMandatoryParams = signature.getSignature().getShape().getMandatoryPositionals(); |
| |
| 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(FunctionParamInfo.forParam(paramName, paramDoc, /*default param*/ null)); |
| } |
| |
| // Parameters with defaults. |
| if (signature.getDefaultValues() != null) { |
| for (Object element : signature.getDefaultValues()) { |
| String paramName = paramNames.get(paramIndex); |
| String paramDoc = ""; |
| Object defaultParamValue = element; |
| if (paramNameToDocMap.containsKey(paramName)) { |
| paramDoc = paramNameToDocMap.get(paramName); |
| } |
| parameterInfos.add(FunctionParamInfo.forParam(paramName, paramDoc, defaultParamValue)); |
| paramIndex++; |
| } |
| } |
| |
| // *arg |
| if (signature.getSignature().getShape().hasStarArg()) { |
| 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(FunctionParamInfo.forSpecialParam(paramName, paramDoc)); |
| paramIndex++; |
| } |
| |
| // **kwargs |
| if (signature.getSignature().getShape().hasKwArg()) { |
| 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(FunctionParamInfo.forSpecialParam(paramName, paramDoc)); |
| paramIndex++; |
| } |
| return parameterInfos.build(); |
| } |
| |
| private UserDefinedFunctionInfo( |
| String functionName, Collection<FunctionParamInfo> parameters, String docString) { |
| this.functionName = functionName; |
| this.parameters = parameters; |
| this.docString = docString; |
| } |
| |
| /** Returns the raw name of this function, for example, "my_function". */ |
| public String getName() { |
| return functionName; |
| } |
| |
| /** |
| * Returns a collection of {@link FunctionParamInfo} objects, where each encapsulates information |
| * about what of the function parameters. Ordering matches the actual Starlark function signature. |
| */ |
| public Collection<FunctionParamInfo> getParameters() { |
| return parameters; |
| } |
| |
| /** |
| * Returns the portion of the docstring that is not part of any special sections, such as "Args:" |
| * or "Returns:". Returns the empty string if there is no docstring literal for this function. |
| */ |
| public String getDocString() { |
| return docString; |
| } |
| } |