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 }