github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/codegen/docs/gen_function.go (about)

     1  // Copyright 2016-2020, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Pulling out some of the repeated strings tokens into constants would harm readability, so we just ignore the
    16  // goconst linter's warning.
    17  //
    18  // nolint: lll, goconst
    19  package docs
    20  
    21  import (
    22  	"bytes"
    23  	"fmt"
    24  	"strings"
    25  
    26  	"github.com/pulumi/pulumi/pkg/v3/codegen"
    27  	go_gen "github.com/pulumi/pulumi/pkg/v3/codegen/go"
    28  	"github.com/pulumi/pulumi/pkg/v3/codegen/python"
    29  	"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
    30  )
    31  
    32  // functionDocArgs represents the args that a Function doc template needs.
    33  type functionDocArgs struct {
    34  	Header header
    35  
    36  	Tool string
    37  
    38  	DeprecationMessage string
    39  	Comment            string
    40  	ExamplesSection    []exampleSection
    41  
    42  	// FunctionName is a map of the language and the function name in that language.
    43  	FunctionName map[string]string
    44  	// FunctionArgs is map per language view of the parameters
    45  	// in the Function.
    46  	FunctionArgs map[string]string
    47  	// FunctionResult is a map per language property types
    48  	// that is returned as a result of calling a Function.
    49  	FunctionResult map[string]propertyType
    50  
    51  	// InputProperties is a map per language and the corresponding slice
    52  	// of input properties accepted by the Function.
    53  	InputProperties map[string][]property
    54  	// InputProperties is a map per language and the corresponding slice
    55  	// of output properties, which are properties of the FunctionResult type.
    56  	OutputProperties map[string][]property
    57  
    58  	// NestedTypes is a slice of the nested types used in the input and
    59  	// output properties.
    60  	NestedTypes []docNestedType
    61  
    62  	PackageDetails packageDetails
    63  
    64  	// Check if the function supports an `Output` version that is
    65  	// automatically lifted to accept `Input` values and return an
    66  	// `Output` (per language).
    67  	HasOutputVersion map[string]bool
    68  
    69  	// True if any of the entries in `HasOutputVersion` are true.
    70  	AnyLanguageHasOutputVersion bool
    71  
    72  	// Same as FunctionArgs, but specific to the Output version of
    73  	// the function.
    74  	FunctionArgsOutputVersion map[string]string
    75  
    76  	// Same as FunctionResult, but specific to the Output version
    77  	// of the function. In languages like Go, `Output<Result>`
    78  	// gets a dedicated nominal type to emulate generics, which
    79  	// will be passed in here.
    80  	FunctionResultOutputVersion map[string]propertyType
    81  }
    82  
    83  // getFunctionResourceInfo returns a map of per-language information about
    84  // the resource being looked-up using a static "getter" function.
    85  func (mod *modContext) getFunctionResourceInfo(f *schema.Function, outputVersion bool) map[string]propertyType {
    86  	dctx := mod.docGenContext
    87  	resourceMap := make(map[string]propertyType)
    88  
    89  	var resultTypeName string
    90  	for _, lang := range dctx.supportedLanguages {
    91  		docLangHelper := dctx.getLanguageDocHelper(lang)
    92  		switch lang {
    93  		case "nodejs":
    94  			resultTypeName = docLangHelper.GetResourceFunctionResultName(mod.mod, f)
    95  		case "go":
    96  			resultTypeName = docLangHelper.GetResourceFunctionResultName(mod.mod, f)
    97  			if outputVersion {
    98  				resultTypeName = fmt.Sprintf("%sOutput", resultTypeName)
    99  			}
   100  		case "csharp":
   101  			namespace := title(mod.pkg.Name, lang)
   102  			if ns, ok := dctx.csharpPkgInfo.Namespaces[mod.pkg.Name]; ok {
   103  				namespace = ns
   104  			}
   105  			resultTypeName = docLangHelper.GetResourceFunctionResultName(mod.mod, f)
   106  			if mod.mod == "" {
   107  				resultTypeName = fmt.Sprintf("Pulumi.%s.%s", namespace, resultTypeName)
   108  			} else {
   109  				resultTypeName = fmt.Sprintf("Pulumi.%s.%s.%s", namespace, title(mod.mod, lang), resultTypeName)
   110  			}
   111  
   112  		case "python":
   113  			resultTypeName = docLangHelper.GetResourceFunctionResultName(mod.mod, f)
   114  		case "java":
   115  			resultTypeName = docLangHelper.GetResourceFunctionResultName(mod.mod, f)
   116  		case "yaml":
   117  			resultTypeName = docLangHelper.GetResourceFunctionResultName(mod.mod, f)
   118  		default:
   119  			panic(fmt.Errorf("cannot generate function resource info for unhandled language %q", lang))
   120  		}
   121  
   122  		parts := strings.Split(resultTypeName, ".")
   123  		displayName := parts[len(parts)-1]
   124  		resourceMap[lang] = propertyType{
   125  			Name:        resultTypeName,
   126  			DisplayName: displayName,
   127  			Link:        "#result",
   128  		}
   129  	}
   130  
   131  	return resourceMap
   132  }
   133  
   134  func (mod *modContext) genFunctionTS(f *schema.Function, funcName string, outputVersion bool) []formalParam {
   135  	dctx := mod.docGenContext
   136  
   137  	argsTypeSuffix := "Args"
   138  	if outputVersion {
   139  		argsTypeSuffix = "OutputArgs"
   140  	}
   141  
   142  	argsType := title(fmt.Sprintf("%s%s", funcName, argsTypeSuffix), "nodejs")
   143  
   144  	docLangHelper := dctx.getLanguageDocHelper("nodejs")
   145  	var params []formalParam
   146  	if f.Inputs != nil {
   147  		params = append(params, formalParam{
   148  			Name:         "args",
   149  			OptionalFlag: "",
   150  			Type: propertyType{
   151  				Name: argsType,
   152  			},
   153  		})
   154  	}
   155  	params = append(params, formalParam{
   156  		Name:         "opts",
   157  		OptionalFlag: "?",
   158  		Type: propertyType{
   159  			Name: "InvokeOptions",
   160  			Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, "InvokeOptions"),
   161  		},
   162  	})
   163  
   164  	return params
   165  }
   166  
   167  func (mod *modContext) genFunctionGo(f *schema.Function, funcName string, outputVersion bool) []formalParam {
   168  	argsTypeSuffix := "Args"
   169  	if outputVersion {
   170  		argsTypeSuffix = "OutputArgs"
   171  	}
   172  
   173  	argsType := fmt.Sprintf("%s%s", funcName, argsTypeSuffix)
   174  
   175  	params := []formalParam{
   176  		{
   177  			Name:         "ctx",
   178  			OptionalFlag: "*",
   179  			Type: propertyType{
   180  				Name: "Context",
   181  				Link: "https://pkg.go.dev/github.com/pulumi/pulumi/sdk/v3/go/pulumi?tab=doc#Context",
   182  			},
   183  		},
   184  	}
   185  
   186  	if f.Inputs != nil {
   187  		params = append(params, formalParam{
   188  			Name:         "args",
   189  			OptionalFlag: "*",
   190  			Type: propertyType{
   191  				Name: argsType,
   192  			},
   193  		})
   194  	}
   195  
   196  	params = append(params, formalParam{
   197  		Name:         "opts",
   198  		OptionalFlag: "...",
   199  		Type: propertyType{
   200  			Name: "InvokeOption",
   201  			Link: "https://pkg.go.dev/github.com/pulumi/pulumi/sdk/v3/go/pulumi?tab=doc#InvokeOption",
   202  		},
   203  	})
   204  	return params
   205  }
   206  
   207  func (mod *modContext) genFunctionCS(f *schema.Function, funcName string, outputVersion bool) []formalParam {
   208  	dctx := mod.docGenContext
   209  
   210  	argsTypeSuffix := "Args"
   211  	if outputVersion {
   212  		argsTypeSuffix = "InvokeArgs"
   213  
   214  	}
   215  
   216  	argsType := funcName + argsTypeSuffix
   217  	docLangHelper := dctx.getLanguageDocHelper("csharp")
   218  	var params []formalParam
   219  	if f.Inputs != nil {
   220  		params = append(params, formalParam{
   221  			Name:         "args",
   222  			OptionalFlag: "",
   223  			DefaultValue: "",
   224  			Type: propertyType{
   225  				Name: argsType,
   226  			},
   227  		})
   228  	}
   229  
   230  	params = append(params, formalParam{
   231  		Name:         "opts",
   232  		OptionalFlag: "?",
   233  		DefaultValue: " = null",
   234  		Type: propertyType{
   235  			Name: "InvokeOptions",
   236  			Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, "Pulumi.InvokeOptions"),
   237  		},
   238  	})
   239  	return params
   240  }
   241  
   242  func (mod *modContext) genFunctionJava(f *schema.Function, funcName string, outputVersion bool) []formalParam {
   243  	dctx := mod.docGenContext
   244  
   245  	argsTypeSuffix := "Args"
   246  	if outputVersion {
   247  		argsTypeSuffix = "InvokeArgs"
   248  
   249  	}
   250  
   251  	argsType := title(funcName+argsTypeSuffix, "java")
   252  	docLangHelper := dctx.getLanguageDocHelper("java")
   253  	var params []formalParam
   254  	if f.Inputs != nil {
   255  		params = append(params, formalParam{
   256  			Name:         "args",
   257  			OptionalFlag: "",
   258  			DefaultValue: "",
   259  			Type: propertyType{
   260  				Name: argsType,
   261  			},
   262  		})
   263  	}
   264  
   265  	params = append(params, formalParam{
   266  		Name:         "options",
   267  		OptionalFlag: "@Nullable",
   268  		Type: propertyType{
   269  			Name: "InvokeOptions",
   270  			Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, "InvokeOptions"),
   271  		},
   272  	})
   273  	return params
   274  }
   275  
   276  func (mod *modContext) genFunctionPython(f *schema.Function, resourceName string, outputVersion bool) []formalParam {
   277  	dctx := mod.docGenContext
   278  	docLanguageHelper := dctx.getLanguageDocHelper("python")
   279  	var params []formalParam
   280  
   281  	// Some functions don't have any inputs other than the InvokeOptions.
   282  	// For example, the `get_billing_service_account` function.
   283  	if f.Inputs != nil {
   284  
   285  		inputs := f.Inputs
   286  		if outputVersion {
   287  			inputs = inputs.InputShape
   288  		}
   289  
   290  		params = make([]formalParam, 0, len(inputs.Properties))
   291  		for _, prop := range inputs.Properties {
   292  
   293  			var schemaType schema.Type
   294  			if outputVersion {
   295  				schemaType = codegen.OptionalType(prop)
   296  			} else {
   297  				schemaType = codegen.PlainType(codegen.OptionalType(prop))
   298  			}
   299  
   300  			typ := docLanguageHelper.GetLanguageTypeString(mod.pkg, mod.mod,
   301  				schemaType, true /*input*/)
   302  			params = append(params, formalParam{
   303  				Name:         python.PyName(prop.Name),
   304  				DefaultValue: " = None",
   305  				Type: propertyType{
   306  					Name: typ,
   307  				},
   308  			})
   309  		}
   310  	} else {
   311  		params = make([]formalParam, 0, 1)
   312  	}
   313  
   314  	params = append(params, formalParam{
   315  		Name:         "opts",
   316  		DefaultValue: " = None",
   317  		Type: propertyType{
   318  			Name: "Optional[InvokeOptions]",
   319  			Link: "/docs/reference/pkg/python/pulumi/#pulumi.InvokeOptions",
   320  		},
   321  	})
   322  
   323  	return params
   324  }
   325  
   326  // genFunctionArgs generates the arguments string for a given Function that can be
   327  // rendered directly into a template.
   328  func (mod *modContext) genFunctionArgs(f *schema.Function, funcNameMap map[string]string, outputVersion bool) map[string]string {
   329  	dctx := mod.docGenContext
   330  	functionParams := make(map[string]string)
   331  
   332  	for _, lang := range dctx.supportedLanguages {
   333  		var (
   334  			paramTemplate string
   335  			params        []formalParam
   336  		)
   337  		b := &bytes.Buffer{}
   338  
   339  		paramSeparatorTemplate := "param_separator"
   340  		ps := paramSeparator{}
   341  
   342  		switch lang {
   343  		case "nodejs":
   344  			params = mod.genFunctionTS(f, funcNameMap["nodejs"], outputVersion)
   345  			paramTemplate = "ts_formal_param"
   346  		case "go":
   347  			params = mod.genFunctionGo(f, funcNameMap["go"], outputVersion)
   348  			paramTemplate = "go_formal_param"
   349  		case "csharp":
   350  			params = mod.genFunctionCS(f, funcNameMap["csharp"], outputVersion)
   351  			paramTemplate = "csharp_formal_param"
   352  		case "java":
   353  			params = mod.genFunctionJava(f, funcNameMap["java"], outputVersion)
   354  			paramTemplate = "java_formal_param"
   355  		case "yaml":
   356  			// Left blank
   357  		case "python":
   358  			params = mod.genFunctionPython(f, funcNameMap["python"], outputVersion)
   359  			paramTemplate = "py_formal_param"
   360  			paramSeparatorTemplate = "py_param_separator"
   361  
   362  			docHelper := dctx.getLanguageDocHelper(lang)
   363  			funcName := docHelper.GetFunctionName(mod.mod, f)
   364  			ps = paramSeparator{Indent: strings.Repeat(" ", len("def (")+len(funcName))}
   365  		}
   366  
   367  		n := len(params)
   368  		if n == 0 {
   369  			continue
   370  		}
   371  
   372  		for i, p := range params {
   373  			if err := dctx.templates.ExecuteTemplate(b, paramTemplate, p); err != nil {
   374  				panic(err)
   375  			}
   376  			if i != n-1 {
   377  				if err := dctx.templates.ExecuteTemplate(b, paramSeparatorTemplate, ps); err != nil {
   378  					panic(err)
   379  				}
   380  			}
   381  		}
   382  		functionParams[lang] = b.String()
   383  	}
   384  	return functionParams
   385  }
   386  
   387  func (mod *modContext) genFunctionHeader(f *schema.Function) header {
   388  	funcName := tokenToName(f.Token)
   389  	var baseDescription string
   390  	var titleTag string
   391  	if mod.mod == "" {
   392  		baseDescription = fmt.Sprintf("Documentation for the %s.%s function "+
   393  			"with examples, input properties, output properties, "+
   394  			"and supporting types.", mod.pkg.Name, funcName)
   395  		titleTag = fmt.Sprintf("%s.%s", mod.pkg.Name, funcName)
   396  	} else {
   397  		baseDescription = fmt.Sprintf("Documentation for the %s.%s.%s function "+
   398  			"with examples, input properties, output properties, "+
   399  			"and supporting types.", mod.pkg.Name, mod.mod, funcName)
   400  		titleTag = fmt.Sprintf("%s.%s.%s", mod.pkg.Name, mod.mod, funcName)
   401  	}
   402  
   403  	return header{
   404  		Title:    funcName,
   405  		TitleTag: titleTag,
   406  		MetaDesc: baseDescription,
   407  	}
   408  }
   409  
   410  func (mod *modContext) genFunctionOutputVersionMap(f *schema.Function) map[string]bool {
   411  	dctx := mod.docGenContext
   412  	result := map[string]bool{}
   413  	for _, lang := range dctx.supportedLanguages {
   414  		hasOutputVersion := f.NeedsOutputVersion()
   415  		if lang == "go" {
   416  			hasOutputVersion = go_gen.NeedsGoOutputVersion(f)
   417  		}
   418  		if lang == "java" || lang == "yaml" {
   419  			hasOutputVersion = false
   420  		}
   421  		result[lang] = hasOutputVersion
   422  	}
   423  	return result
   424  }
   425  
   426  // genFunction is the main entrypoint for generating docs for a Function.
   427  // Returns args type that can be used to execute the `function.tmpl` doc template.
   428  func (mod *modContext) genFunction(f *schema.Function) functionDocArgs {
   429  	dctx := mod.docGenContext
   430  	inputProps := make(map[string][]property)
   431  	outputProps := make(map[string][]property)
   432  	for _, lang := range dctx.supportedLanguages {
   433  		if f.Inputs != nil {
   434  			inputProps[lang] = mod.getProperties(f.Inputs.Properties, lang, true, false, false)
   435  		}
   436  		if f.Outputs != nil {
   437  			outputProps[lang] = mod.getProperties(f.Outputs.Properties, lang, false, false, false)
   438  		}
   439  	}
   440  
   441  	nestedTypes := mod.genNestedTypes(f, false /*resourceType*/)
   442  
   443  	// Generate the per-language map for the function name.
   444  	funcNameMap := map[string]string{}
   445  	for _, lang := range dctx.supportedLanguages {
   446  		docHelper := dctx.getLanguageDocHelper(lang)
   447  		funcNameMap[lang] = docHelper.GetFunctionName(mod.mod, f)
   448  	}
   449  
   450  	packageDetails := packageDetails{
   451  		Repository: mod.pkg.Repository,
   452  		License:    mod.pkg.License,
   453  		Notes:      mod.pkg.Attribution,
   454  	}
   455  
   456  	docInfo := dctx.decomposeDocstring(f.Comment)
   457  	args := functionDocArgs{
   458  		Header: mod.genFunctionHeader(f),
   459  
   460  		Tool: mod.tool,
   461  
   462  		FunctionName:   funcNameMap,
   463  		FunctionArgs:   mod.genFunctionArgs(f, funcNameMap, false /*outputVersion*/),
   464  		FunctionResult: mod.getFunctionResourceInfo(f, false /*outputVersion*/),
   465  
   466  		Comment:            docInfo.description,
   467  		DeprecationMessage: f.DeprecationMessage,
   468  		ExamplesSection:    docInfo.examples,
   469  
   470  		InputProperties:  inputProps,
   471  		OutputProperties: outputProps,
   472  
   473  		NestedTypes: nestedTypes,
   474  
   475  		PackageDetails: packageDetails,
   476  	}
   477  
   478  	args.HasOutputVersion = mod.genFunctionOutputVersionMap(f)
   479  
   480  	for _, hasOutputVersion := range args.HasOutputVersion {
   481  		if hasOutputVersion {
   482  			args.AnyLanguageHasOutputVersion = true
   483  			continue
   484  		}
   485  	}
   486  
   487  	if f.NeedsOutputVersion() {
   488  		args.FunctionArgsOutputVersion = mod.genFunctionArgs(f, funcNameMap, true /*outputVersion*/)
   489  		args.FunctionResultOutputVersion = mod.getFunctionResourceInfo(f, true /*outputVersion*/)
   490  	}
   491  
   492  	return args
   493  }