github.com/cdmixer/woolloomooloo@v0.1.0/pkg/codegen/docs/gen.go (about)

     1  //go:generate go run bundler.go
     2  
     3  // Copyright 2016-2020, Pulumi Corporation.
     4  //
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  // Pulling out some of the repeated strings tokens into constants would harm readability, so we just ignore the
    18  // goconst linter's warning.
    19  //
    20  // nolint: lll, goconst
    21  package docs
    22  
    23  import (
    24  	"bytes"
    25  	"fmt"
    26  	"html"
    27  	"html/template"
    28  	"path"
    29  	"regexp"
    30  	"sort"
    31  	"strings"
    32  
    33  	"github.com/golang/glog"
    34  	"github.com/pkg/errors"
    35  
    36  	"github.com/pulumi/pulumi/pkg/v2/codegen"
    37  	"github.com/pulumi/pulumi/pkg/v2/codegen/dotnet"
    38  	go_gen "github.com/pulumi/pulumi/pkg/v2/codegen/go"
    39  	"github.com/pulumi/pulumi/pkg/v2/codegen/nodejs"
    40  	"github.com/pulumi/pulumi/pkg/v2/codegen/python"
    41  	"github.com/pulumi/pulumi/pkg/v2/codegen/schema"
    42  	"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
    43  )
    44  
    45  var (
    46  	supportedLanguages = []string{"csharp", "go", "nodejs", "python"}
    47  	snippetLanguages   = []string{"csharp", "go", "python", "typescript"}
    48  	templates          *template.Template
    49  	packagedTemplates  map[string][]byte
    50  	docHelpers         map[string]codegen.DocLanguageHelper
    51  
    52  	// The following property case maps are for rendering property
    53  	// names of nested properties in Python language with the correct
    54  	// casing.
    55  	snakeCaseToCamelCase map[string]string
    56  	camelCaseToSnakeCase map[string]string
    57  	seenCasingTypes      codegen.Set
    58  
    59  	// The language-specific info objects for a certain package (provider).
    60  	goPkgInfo     go_gen.GoPackageInfo
    61  	csharpPkgInfo dotnet.CSharpPackageInfo
    62  	nodePkgInfo   nodejs.NodePackageInfo
    63  	pythonPkgInfo python.PackageInfo
    64  
    65  	// langModuleNameLookup is a map of module name to its language-specific
    66  	// name.
    67  	langModuleNameLookup map[string]string
    68  	// titleLookup is a map to map module package name to the desired display name
    69  	// for display in the TOC menu under API Reference.
    70  	titleLookup = map[string]string{
    71  		"aiven":         "Aiven",
    72  		"akamai":        "Akamai",
    73  		"alicloud":      "AliCloud",
    74  		"auth0":         "Auth0",
    75  		"aws":           "AWS",
    76  		"azure":         "Azure",
    77  		"azure-nextgen": "Azure NextGen",
    78  		"azuread":       "Azure AD",
    79  		"azuredevops":   "Azure DevOps",
    80  		"azuresel":      "Azure",
    81  		"civo":          "Civo",
    82  		"cloudamqp":     "CloudAMQP",
    83  		"cloudflare":    "Cloudflare",
    84  		"consul":        "Consul",
    85  		"datadog":       "Datadog",
    86  		"digitalocean":  "DigitalOcean",
    87  		"dnsimple":      "DNSimple",
    88  		"docker":        "Docker",
    89  		"f5bigip":       "f5 BIG-IP",
    90  		"fastly":        "Fastly",
    91  		"gcp":           "GCP",
    92  		"github":        "GitHub",
    93  		"gitlab":        "GitLab",
    94  		"hcloud":        "Hetzner Cloud",
    95  		"kafka":         "Kafka",
    96  		"keycloak":      "Keycloak",
    97  		"kong":          "Kong",
    98  		"kubernetes":    "Kubernetes",
    99  		"linode":        "Linode",
   100  		"mailgun":       "Mailgun",
   101  		"mongodbatlas":  "MongoDB Atlas",
   102  		"mysql":         "MySQL",
   103  		"newrelic":      "New Relic",
   104  		"ns1":           "NS1",
   105  		"okta":          "Okta",
   106  		"openstack":     "Open Stack",
   107  		"packet":        "Packet",
   108  		"pagerduty":     "PagerDuty",
   109  		"postgresql":    "PostgreSQL",
   110  		"rabbitmq":      "RabbitMQ",
   111  		"rancher2":      "Rancher 2",
   112  		"random":        "Random",
   113  		"signalfx":      "SignalFx",
   114  		"spotinst":      "Spotinst",
   115  		"tls":           "TLS",
   116  		"vault":         "Vault",
   117  		"venafi":        "Venafi",
   118  		"vsphere":       "vSphere",
   119  		"wavefront":     "Wavefront",
   120  	}
   121  	// metaDescriptionRegexp attempts to extract the description from Resource.Comment.
   122  	// Extracts the first line, essentially the "human-friendly" part of the description.
   123  	metaDescriptionRegexp = regexp.MustCompile(`(?m)^.*$`)
   124  	// Property anchor tag separator, used in a property anchor tag id to separate the
   125  	// property and language (e.g. property~lang).
   126  	propertyLangSeparator = "_"
   127  )
   128  
   129  func init() {
   130  	docHelpers = make(map[string]codegen.DocLanguageHelper)
   131  	for _, lang := range supportedLanguages {
   132  		switch lang {
   133  		case "csharp":
   134  			docHelpers[lang] = &dotnet.DocLanguageHelper{}
   135  		case "go":
   136  			docHelpers[lang] = &go_gen.DocLanguageHelper{}
   137  		case "nodejs":
   138  			docHelpers[lang] = &nodejs.DocLanguageHelper{}
   139  		case "python":
   140  			docHelpers[lang] = &python.DocLanguageHelper{}
   141  		}
   142  	}
   143  
   144  	snakeCaseToCamelCase = map[string]string{}
   145  	camelCaseToSnakeCase = map[string]string{}
   146  	seenCasingTypes = codegen.Set{}
   147  	langModuleNameLookup = map[string]string{}
   148  }
   149  
   150  // header represents the header of each resource markdown file.
   151  type header struct {
   152  	Title    string
   153  	TitleTag string
   154  	MetaDesc string
   155  }
   156  
   157  // property represents an input or an output property.
   158  type property struct {
   159  	// ID is the `id` attribute that will be attached to the DOM element containing the property.
   160  	ID string
   161  	// DisplayName is the property name with word-breaks.
   162  	DisplayName        string
   163  	Name               string
   164  	Comment            string
   165  	Types              []propertyType
   166  	DeprecationMessage string
   167  	Link               string
   168  
   169  	IsRequired bool
   170  	IsInput    bool
   171  }
   172  
   173  // apiTypeDocLinks represents the links for a type's input and output API doc.
   174  type apiTypeDocLinks struct {
   175  	InputType  string
   176  	OutputType string
   177  }
   178  
   179  // docNestedType represents a complex type.
   180  type docNestedType struct {
   181  	Name        string
   182  	AnchorID    string
   183  	APIDocLinks map[string]apiTypeDocLinks
   184  	Properties  map[string][]property
   185  }
   186  
   187  // propertyType represents the type of a property.
   188  type propertyType struct {
   189  	DisplayName string
   190  	Name        string
   191  	// Link can be a link to an anchor tag on the same
   192  	// page, or to another page/site.
   193  	Link string
   194  }
   195  
   196  // formalParam represents the formal parameters of a constructor
   197  // or a lookup function.
   198  type formalParam struct {
   199  	Name string
   200  	Type propertyType
   201  
   202  	// This is the language specific optional type indicator.
   203  	// For example, in nodejs this is the character "?" and in Go
   204  	// it's "*".
   205  	OptionalFlag string
   206  
   207  	DefaultValue string
   208  
   209  	// Comment is an optional description of the parameter.
   210  	Comment string
   211  }
   212  
   213  type packageDetails struct {
   214  	Repository string
   215  	License    string
   216  	Notes      string
   217  	Version    string
   218  }
   219  
   220  type resourceDocArgs struct {
   221  	Header header
   222  
   223  	Tool string
   224  	// LangChooserLanguages is a comma-separated list of languages to pass to the
   225  	// language chooser shortcode. Use this to customize the languages shown for a
   226  	// resource. By default, the language chooser will show all languages supported
   227  	// by Pulumi for all resources.
   228  	LangChooserLanguages string
   229  
   230  	// Comment represents the introductory resource comment.
   231  	Comment            string
   232  	ExamplesSection    []exampleSection
   233  	DeprecationMessage string
   234  
   235  	// Import
   236  	ImportDocs string
   237  
   238  	// ConstructorParams is a map from language to the rendered HTML for the constructor's
   239  	// arguments.
   240  	ConstructorParams map[string]string
   241  	// ConstructorParamsTyped is the typed set of parameters for the constructor, in order.
   242  	ConstructorParamsTyped map[string][]formalParam
   243  	// ConstructorResource is the resource that is being constructed or
   244  	// is the result of a constructor-like function.
   245  	ConstructorResource map[string]propertyType
   246  	// ArgsRequired is a flag indicating if the args param is required
   247  	// when creating a new resource.
   248  	ArgsRequired bool
   249  
   250  	// InputProperties is a map per language and a corresponding slice of
   251  	// input properties accepted as args while creating a new resource.
   252  	InputProperties map[string][]property
   253  	// OutputProperties is a map per language and a corresponding slice of
   254  	// output properties returned when a new instance of the resource is
   255  	// created.
   256  	OutputProperties map[string][]property
   257  
   258  	// LookupParams is a map of the param string to be rendered per language
   259  	// for looking-up a resource.
   260  	LookupParams map[string]string
   261  	// StateInputs is a map per language and the corresponding slice of
   262  	// state input properties required while looking-up an existing resource.
   263  	StateInputs map[string][]property
   264  	// StateParam is the type name of the state param, if any.
   265  	StateParam string
   266  
   267  	// NestedTypes is a slice of the nested types used in the input and
   268  	// output properties.
   269  	NestedTypes []docNestedType
   270  
   271  	PackageDetails packageDetails
   272  }
   273  
   274  // typeUsage represents a nested type's usage.
   275  type typeUsage struct {
   276  	Input  bool
   277  	Output bool
   278  }
   279  
   280  // nestedTypeUsageInfo is a type-alias for a map of Pulumi type-tokens
   281  // and whether or not the type is used as an input and/or output
   282  // properties.
   283  type nestedTypeUsageInfo map[string]typeUsage
   284  
   285  func (ss nestedTypeUsageInfo) add(s string, input bool) {
   286  	if v, ok := ss[s]; ok {
   287  		if input {
   288  			v.Input = true
   289  		} else {
   290  			v.Output = true
   291  		}
   292  		ss[s] = v
   293  		return
   294  	}
   295  
   296  	ss[s] = typeUsage{
   297  		Input:  input,
   298  		Output: !input,
   299  	}
   300  }
   301  
   302  // contains returns true if the token already exists and matches the
   303  // input or output flag of the token.
   304  func (ss nestedTypeUsageInfo) contains(token string, input bool) bool {
   305  	a, ok := ss[token]
   306  	if !ok {
   307  		return false
   308  	}
   309  
   310  	if input && a.Input {
   311  		return true
   312  	} else if !input && a.Output {
   313  		return true
   314  	}
   315  	return false
   316  }
   317  
   318  type modContext struct {
   319  	pkg          *schema.Package
   320  	mod          string
   321  	resources    []*schema.Resource
   322  	functions    []*schema.Function
   323  	children     []*modContext
   324  	tool         string
   325  	emitAPILinks bool
   326  }
   327  
   328  func resourceName(r *schema.Resource) string {
   329  	if r.IsProvider {
   330  		return "Provider"
   331  	}
   332  	return strings.Title(tokenToName(r.Token))
   333  }
   334  
   335  func getLanguageDocHelper(lang string) codegen.DocLanguageHelper {
   336  	if h, ok := docHelpers[lang]; ok {
   337  		return h
   338  	}
   339  	panic(errors.Errorf("could not find a doc lang helper for %s", lang))
   340  }
   341  
   342  type propertyCharacteristics struct {
   343  	// input is a flag indicating if the property is an input type.
   344  	input bool
   345  	// optional is a flag indicating if the property is optional.
   346  	optional bool
   347  }
   348  
   349  // getLanguageModuleName transforms the current module's name to a
   350  // language-specific name using the language info, if any, for the
   351  // current package.
   352  func (mod *modContext) getLanguageModuleName(lang string) string {
   353  	modName := mod.mod
   354  	lookupKey := lang + "_" + modName
   355  	if v, ok := langModuleNameLookup[lookupKey]; ok {
   356  		return v
   357  	}
   358  
   359  	switch lang {
   360  	case "go":
   361  		// Go module names use lowercase.
   362  		modName = strings.ToLower(modName)
   363  		if override, ok := goPkgInfo.ModuleToPackage[modName]; ok {
   364  			modName = override
   365  		}
   366  	case "csharp":
   367  		if override, ok := csharpPkgInfo.Namespaces[modName]; ok {
   368  			modName = override
   369  		}
   370  	case "nodejs":
   371  		if override, ok := nodePkgInfo.ModuleToPackage[modName]; ok {
   372  			modName = override
   373  		}
   374  	case "python":
   375  		if override, ok := pythonPkgInfo.ModuleNameOverrides[modName]; ok {
   376  			modName = override
   377  		}
   378  	}
   379  
   380  	langModuleNameLookup[lookupKey] = modName
   381  	return modName
   382  }
   383  
   384  // cleanTypeString removes any namespaces from the generated type string for all languages.
   385  // The result of this function should be used display purposes only.
   386  func (mod *modContext) cleanTypeString(t schema.Type, langTypeString, lang, modName string, isInput bool) string {
   387  	switch lang {
   388  	case "go", "python":
   389  		langTypeString = cleanOptionalIdentifier(langTypeString, lang)
   390  		parts := strings.Split(langTypeString, ".")
   391  		return parts[len(parts)-1]
   392  	}
   393  
   394  	cleanCSharpName := func(pkgName, objModName string) string {
   395  		// C# types can be wrapped in enumerable types such as List<> or Dictionary<>, so we have to
   396  		// only replace the namespace between the < and the > characters.
   397  		qualifier := "Inputs"
   398  		if !isInput {
   399  			qualifier = "Outputs"
   400  		}
   401  
   402  		var csharpNS string
   403  		// This type could be at the package-level, so it won't have a module name.
   404  		if objModName != "" {
   405  			csharpNS = fmt.Sprintf("Pulumi.%s.%s.%s.", title(pkgName, lang), title(objModName, lang), qualifier)
   406  		} else {
   407  			csharpNS = fmt.Sprintf("Pulumi.%s.%s.", title(pkgName, lang), qualifier)
   408  		}
   409  		return strings.ReplaceAll(langTypeString, csharpNS, "")
   410  	}
   411  
   412  	cleanNodeJSName := func(objModName string) string {
   413  		// The nodejs codegen currently doesn't use the ModuleToPackage override available
   414  		// in the k8s package's schema. So we'll manually strip some known module names for k8s.
   415  		// TODO[pulumi/pulumi#4325]: Remove this block once the nodejs code gen is able to use the
   416  		// package name overrides for modules.
   417  		if isKubernetesPackage(mod.pkg) {
   418  			langTypeString = strings.ReplaceAll(langTypeString, "k8s.io.", "")
   419  			langTypeString = strings.ReplaceAll(langTypeString, "apiserver.", "")
   420  			langTypeString = strings.ReplaceAll(langTypeString, "rbac.authorization.v1.", "")
   421  			langTypeString = strings.ReplaceAll(langTypeString, "rbac.authorization.v1alpha1.", "")
   422  			langTypeString = strings.ReplaceAll(langTypeString, "rbac.authorization.v1beta1.", "")
   423  		}
   424  		objModName = strings.ReplaceAll(objModName, "/", ".") + "."
   425  		return strings.ReplaceAll(langTypeString, objModName, "")
   426  	}
   427  
   428  	switch t := t.(type) {
   429  	case *schema.ArrayType:
   430  		if schema.IsPrimitiveType(t.ElementType) {
   431  			break
   432  		}
   433  		return mod.cleanTypeString(t.ElementType, langTypeString, lang, modName, isInput)
   434  	case *schema.UnionType:
   435  		for _, e := range t.ElementTypes {
   436  			if schema.IsPrimitiveType(e) {
   437  				continue
   438  			}
   439  			return mod.cleanTypeString(e, langTypeString, lang, modName, isInput)
   440  		}
   441  	case *schema.ObjectType:
   442  		objTypeModName := mod.pkg.TokenToModule(t.Token)
   443  		if objTypeModName != mod.mod {
   444  			modName = mod.getLanguageModuleName(lang)
   445  		}
   446  	}
   447  
   448  	if lang == "nodejs" {
   449  		return cleanNodeJSName(modName)
   450  	} else if lang == "csharp" {
   451  		return cleanCSharpName(mod.pkg.Name, modName)
   452  	}
   453  	return strings.ReplaceAll(langTypeString, modName, "")
   454  }
   455  
   456  // typeString returns a property type suitable for docs with its display name and the anchor link to
   457  // a type if the type of the property is an array or an object.
   458  func (mod *modContext) typeString(t schema.Type, lang string, characteristics propertyCharacteristics, insertWordBreaks bool) propertyType {
   459  	docLanguageHelper := getLanguageDocHelper(lang)
   460  	modName := mod.getLanguageModuleName(lang)
   461  	langTypeString := docLanguageHelper.GetLanguageTypeString(mod.pkg, modName, t, characteristics.input, characteristics.optional)
   462  
   463  	// If the type is an object type, let's also wrap it with a link to the supporting type
   464  	// on the same page using an anchor tag.
   465  	var href string
   466  	switch t := t.(type) {
   467  	case *schema.ArrayType:
   468  		elementLangType := mod.typeString(t.ElementType, lang, characteristics, false)
   469  		href = elementLangType.Link
   470  	case *schema.ObjectType:
   471  		tokenName := tokenToName(t.Token)
   472  		// Links to anchor tags on the same page must be lower-cased.
   473  		href = "#" + strings.ToLower(tokenName)
   474  	default:
   475  		// Check if type is primitive/built-in type if no match for cases listed above.
   476  		if schema.IsPrimitiveType(t) {
   477  			href = docLanguageHelper.GetDocLinkForBuiltInType(t.String())
   478  		}
   479  	}
   480  
   481  	// Strip the namespace/module prefix for the type's display name.
   482  	displayName := langTypeString
   483  	if !schema.IsPrimitiveType(t) {
   484  		displayName = mod.cleanTypeString(t, langTypeString, lang, modName, characteristics.input)
   485  	}
   486  
   487  	// If word-breaks need to be inserted, then the type string
   488  	// should be html-encoded first if the language is C# in order
   489  	// to avoid confusing the Hugo rendering where the word-break
   490  	// tags are inserted.
   491  	if insertWordBreaks {
   492  		if lang == "csharp" {
   493  			displayName = html.EscapeString(displayName)
   494  		}
   495  		displayName = wbr(displayName)
   496  	}
   497  
   498  	displayName = cleanOptionalIdentifier(displayName, lang)
   499  	langTypeString = cleanOptionalIdentifier(langTypeString, lang)
   500  
   501  	return propertyType{
   502  		Name:        langTypeString,
   503  		DisplayName: displayName,
   504  		Link:        href,
   505  	}
   506  }
   507  
   508  // cleanOptionalIdentifier removes the type identifier (i.e. "?" in "string?").
   509  func cleanOptionalIdentifier(s, lang string) string {
   510  	switch lang {
   511  	case "nodejs":
   512  		return strings.TrimSuffix(s, "?")
   513  	case "go":
   514  		return strings.TrimPrefix(s, "*")
   515  	case "csharp":
   516  		return strings.TrimSuffix(s, "?")
   517  	case "python":
   518  		if strings.HasPrefix(s, "Optional[") && strings.HasSuffix(s, "]") {
   519  			s = strings.TrimPrefix(s, "Optional[")
   520  			s = strings.TrimSuffix(s, "]")
   521  			return s
   522  		}
   523  	}
   524  	return s
   525  }
   526  
   527  // Resources typically take the same set of parameters to their constructors, and these
   528  // are the default comments/descriptions for them.
   529  const (
   530  	ctorNameArgComment = "The unique name of the resource."
   531  	ctorArgsArgComment = "The arguments to resource properties."
   532  	ctorOptsArgComment = "Bag of options to control resource's behavior."
   533  )
   534  
   535  func (mod *modContext) genConstructorTS(r *schema.Resource, argsOptional bool) []formalParam {
   536  	name := resourceName(r)
   537  	docLangHelper := getLanguageDocHelper("nodejs")
   538  	// Use the NodeJS module to package lookup to transform the module name to its normalized package name.
   539  	modName := mod.getLanguageModuleName("nodejs")
   540  
   541  	var argsType string
   542  	var argsDocLink string
   543  	optsType := "CustomResourceOptions"
   544  	// The args type for k8s package differs from the rest depending on whether we are dealing with
   545  	// overlay resources or regular k8s resources.
   546  	if isKubernetesPackage(mod.pkg) {
   547  		if mod.isKubernetesOverlayModule() {
   548  			if name == "CustomResource" {
   549  				argsType = name + "Args"
   550  			} else {
   551  				argsType = name + "Opts"
   552  			}
   553  			argsDocLink = docLangHelper.GetDocLinkForResourceType(mod.pkg, modName, argsType)
   554  		} else {
   555  			// The non-schema-based k8s codegen does not apply a suffix to the input types.
   556  			argsType = name
   557  			// The args types themselves are all under the input types module path, so use the input type link for the args type.
   558  			argsDocLink = docLangHelper.GetDocLinkForResourceInputOrOutputType(mod.pkg, modName, argsType, true)
   559  		}
   560  
   561  		if mod.isComponentResource() {
   562  			optsType = "ComponentResourceOptions"
   563  		}
   564  	} else {
   565  		argsType = name + "Args"
   566  		// All args types are in the same module path as the resource class itself even though it is an "input" type.
   567  		if mod.emitAPILinks {
   568  			argsDocLink = docLangHelper.GetDocLinkForResourceType(mod.pkg, modName, argsType)
   569  		}
   570  	}
   571  
   572  	argsFlag := ""
   573  	if argsOptional {
   574  		argsFlag = "?"
   575  	}
   576  
   577  	return []formalParam{
   578  		{
   579  			Name: "name",
   580  			Type: propertyType{
   581  				Name: "string",
   582  				Link: docLangHelper.GetDocLinkForBuiltInType("string"),
   583  			},
   584  			Comment: ctorNameArgComment,
   585  		},
   586  		{
   587  			Name:         "args",
   588  			OptionalFlag: argsFlag,
   589  			Type: propertyType{
   590  				Name: argsType,
   591  				Link: argsDocLink,
   592  			},
   593  			Comment: ctorArgsArgComment,
   594  		},
   595  		{
   596  			Name:         "opts",
   597  			OptionalFlag: "?",
   598  			Type: propertyType{
   599  				Name: optsType,
   600  				Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, optsType),
   601  			},
   602  			Comment: ctorOptsArgComment,
   603  		},
   604  	}
   605  }
   606  
   607  func (mod *modContext) genConstructorGo(r *schema.Resource, argsOptional bool) []formalParam {
   608  	name := resourceName(r)
   609  	argsType := name + "Args"
   610  	argsFlag := ""
   611  	if argsOptional {
   612  		argsFlag = "*"
   613  	}
   614  
   615  	docLangHelper := getLanguageDocHelper("go")
   616  	// Use the Go module to package lookup to transform the module name to its normalized package name.
   617  	modName := mod.getLanguageModuleName("go")
   618  
   619  	var argsTypeLink string
   620  	if mod.emitAPILinks {
   621  		argsTypeLink = docLangHelper.GetDocLinkForResourceType(mod.pkg, modName, argsType)
   622  	}
   623  
   624  	return []formalParam{
   625  		{
   626  			Name:         "ctx",
   627  			OptionalFlag: "*",
   628  			Type: propertyType{
   629  				Name: "Context",
   630  				Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, "Context"),
   631  			},
   632  			Comment: "Context object for the current deployment.",
   633  		},
   634  		{
   635  			Name: "name",
   636  			Type: propertyType{
   637  				Name: "string",
   638  				Link: docLangHelper.GetDocLinkForBuiltInType("string"),
   639  			},
   640  			Comment: ctorNameArgComment,
   641  		},
   642  		{
   643  			Name:         "args",
   644  			OptionalFlag: argsFlag,
   645  			Type: propertyType{
   646  				Name: argsType,
   647  				Link: argsTypeLink,
   648  			},
   649  			Comment: ctorArgsArgComment,
   650  		},
   651  		{
   652  			Name:         "opts",
   653  			OptionalFlag: "...",
   654  			Type: propertyType{
   655  				Name: "ResourceOption",
   656  				Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, "ResourceOption"),
   657  			},
   658  			Comment: ctorOptsArgComment,
   659  		},
   660  	}
   661  }
   662  
   663  func (mod *modContext) genConstructorCS(r *schema.Resource, argsOptional bool) []formalParam {
   664  	name := resourceName(r)
   665  	argsSchemaType := &schema.ObjectType{
   666  		Token:   r.Token,
   667  		Package: mod.pkg,
   668  	}
   669  	// Get the C#-specific name for the args type, which will be the fully-qualified name.
   670  	characteristics := propertyCharacteristics{
   671  		input:    true,
   672  		optional: argsOptional,
   673  	}
   674  
   675  	var argLangTypeName string
   676  	optsType := "CustomResourceOptions"
   677  
   678  	// Constructor argument types in the k8s package for C# use a different namespace path.
   679  	// K8s overlay resources are in the same namespace path as the resource itself.
   680  	if isKubernetesPackage(mod.pkg) {
   681  		if mod.mod != "" {
   682  			correctModName := mod.getLanguageModuleName("csharp")
   683  			if !mod.isKubernetesOverlayModule() {
   684  				// For k8s, the args type for a resource is part of the `Types.Inputs` namespace.
   685  				argLangTypeName = "Pulumi.Kubernetes.Types.Inputs." + correctModName + "." + name + "Args"
   686  			} else {
   687  				// Helm's resource args type does not use the version number.
   688  				if strings.HasPrefix(mod.mod, "helm") {
   689  					correctModName = "Helm"
   690  				}
   691  				argLangTypeName = "Pulumi.Kubernetes." + correctModName + "." + name + "Args"
   692  			}
   693  		} else {
   694  			argLangTypeName = "Pulumi.Kubernetes." + name + "Args"
   695  		}
   696  
   697  		if mod.isComponentResource() {
   698  			optsType = "ComponentResourceOptions"
   699  		}
   700  	} else {
   701  		argLangType := mod.typeString(argsSchemaType, "csharp", characteristics, false)
   702  		argLangTypeName = strings.ReplaceAll(argLangType.Name, "Inputs.", "")
   703  	}
   704  
   705  	var argsFlag string
   706  	var argsDefault string
   707  	if argsOptional {
   708  		// If the number of required input properties was zero, we can make the args object optional.
   709  		argsDefault = " = null"
   710  		argsFlag = "?"
   711  	}
   712  
   713  	docLangHelper := getLanguageDocHelper("csharp")
   714  
   715  	var argsTypeLink string
   716  	if mod.emitAPILinks {
   717  		argsTypeLink = docLangHelper.GetDocLinkForResourceType(mod.pkg, "", argLangTypeName)
   718  	}
   719  
   720  	return []formalParam{
   721  		{
   722  			Name: "name",
   723  			Type: propertyType{
   724  				Name: "string",
   725  				Link: docLangHelper.GetDocLinkForBuiltInType("string"),
   726  			},
   727  			Comment: ctorNameArgComment,
   728  		},
   729  		{
   730  			Name:         "args",
   731  			OptionalFlag: argsFlag,
   732  			DefaultValue: argsDefault,
   733  			Type: propertyType{
   734  				Name: name + "Args",
   735  				Link: argsTypeLink,
   736  			},
   737  			Comment: ctorArgsArgComment,
   738  		},
   739  		{
   740  			Name:         "opts",
   741  			OptionalFlag: "?",
   742  			DefaultValue: " = null",
   743  			Type: propertyType{
   744  				Name: optsType,
   745  				Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, fmt.Sprintf("Pulumi.%s", optsType)),
   746  			},
   747  			Comment: ctorOptsArgComment,
   748  		},
   749  	}
   750  }
   751  
   752  func (mod *modContext) genConstructorPython(r *schema.Resource, argsOptional bool) []formalParam {
   753  	docLanguageHelper := getLanguageDocHelper("python")
   754  	isK8sOverlayMod := mod.isKubernetesOverlayModule()
   755  	isDockerImageResource := mod.pkg.Name == "docker" && resourceName(r) == "Image"
   756  
   757  	// Kubernetes overlay resources use a different ordering of formal params in Python.
   758  	if isK8sOverlayMod {
   759  		return getKubernetesOverlayPythonFormalParams(mod.mod)
   760  	} else if isDockerImageResource {
   761  		return getDockerImagePythonFormalParams()
   762  	}
   763  
   764  	params := make([]formalParam, 0, len(r.InputProperties)+1)
   765  	// All other resources accept the resource options as a second parameter.
   766  	params = append(params, formalParam{
   767  		Name:         "opts",
   768  		DefaultValue: " = None",
   769  		Type: propertyType{
   770  			Name: "Optional[ResourceOptions]",
   771  			Link: "/docs/reference/pkg/python/pulumi/#pulumi.ResourceOptions",
   772  		},
   773  	})
   774  	for _, p := range r.InputProperties {
   775  		// If the property defines a const value, then skip it.
   776  		// For example, in k8s, `apiVersion` and `kind` are often hard-coded
   777  		// in the SDK and are not really user-provided input properties.
   778  		if p.ConstValue != nil {
   779  			continue
   780  		}
   781  		typ := docLanguageHelper.GetLanguageTypeString(mod.pkg, mod.mod, p.Type, true /*input*/, false /*optional*/)
   782  		params = append(params, formalParam{
   783  			Name:         python.InitParamName(p.Name),
   784  			DefaultValue: " = None",
   785  			Type: propertyType{
   786  				Name: fmt.Sprintf("Optional[%s]", typ),
   787  			},
   788  		})
   789  	}
   790  	return params
   791  }
   792  
   793  func (mod *modContext) genNestedTypes(member interface{}, resourceType bool) []docNestedType {
   794  	tokens := nestedTypeUsageInfo{}
   795  	// Collect all of the types for this "member" as a map of resource names
   796  	// and if it appears in an input object and/or output object.
   797  	mod.getTypes(member, tokens)
   798  
   799  	isK8s := isKubernetesPackage(mod.pkg)
   800  
   801  	var objs []docNestedType
   802  	for token, tyUsage := range tokens {
   803  		for _, t := range mod.pkg.Types {
   804  			obj, ok := t.(*schema.ObjectType)
   805  			if !ok || obj.Token != token {
   806  				continue
   807  			}
   808  			if len(obj.Properties) == 0 {
   809  				continue
   810  			}
   811  
   812  			// Create maps to hold the per-language properties of this object and links to
   813  			// the API doc for each language.
   814  			props := make(map[string][]property)
   815  			apiDocLinks := make(map[string]apiTypeDocLinks)
   816  			for _, lang := range supportedLanguages {
   817  				// The nested type may be under a different package in a language.
   818  				// For example, in k8s, common types are in the core/v1 module and can appear in
   819  				// nested types elsewhere. So we use the appropriate name of that type,
   820  				// as well as its language-specific name. For example, module name for use as a C# namespace
   821  				// or as a Go package name.
   822  				modName := mod.getLanguageModuleName(lang)
   823  				nestedTypeModName := mod.pkg.TokenToModule(token)
   824  				if nestedTypeModName != mod.mod {
   825  					modName = mod.getLanguageModuleName(lang)
   826  				}
   827  
   828  				docLangHelper := getLanguageDocHelper(lang)
   829  				inputCharacteristics := propertyCharacteristics{
   830  					input:    true,
   831  					optional: true,
   832  				}
   833  				outputCharacteristics := propertyCharacteristics{
   834  					input:    false,
   835  					optional: true,
   836  				}
   837  				inputObjLangType := mod.typeString(t, lang, inputCharacteristics, false /*insertWordBreaks*/)
   838  				outputObjLangType := mod.typeString(t, lang, outputCharacteristics, false /*insertWordBreaks*/)
   839  
   840  				// Get the doc link for this nested type based on whether the type is for a Function or a Resource.
   841  				var inputTypeDocLink string
   842  				var outputTypeDocLink string
   843  				if resourceType {
   844  					if tyUsage.Input {
   845  						inputTypeDocLink = docLangHelper.GetDocLinkForResourceInputOrOutputType(mod.pkg, modName, inputObjLangType.Name, true)
   846  					}
   847  					if tyUsage.Output {
   848  						outputTypeDocLink = docLangHelper.GetDocLinkForResourceInputOrOutputType(mod.pkg, modName, outputObjLangType.Name, false)
   849  					}
   850  				} else {
   851  					if tyUsage.Input {
   852  						inputTypeDocLink = docLangHelper.GetDocLinkForFunctionInputOrOutputType(mod.pkg, modName, inputObjLangType.Name, true)
   853  					}
   854  					if tyUsage.Output {
   855  						outputTypeDocLink = docLangHelper.GetDocLinkForFunctionInputOrOutputType(mod.pkg, modName, outputObjLangType.Name, false)
   856  					}
   857  				}
   858  
   859  				props[lang] = mod.getProperties(obj.Properties, lang, true, true)
   860  				// Don't add C# type links for Kubernetes because there are differences in the namespaces between the schema code gen and
   861  				// the current code gen that the package uses. So the links will be incorrect.
   862  				if isK8s && lang == "csharp" {
   863  					continue
   864  				}
   865  				apiDocLinks[lang] = apiTypeDocLinks{
   866  					InputType:  inputTypeDocLink,
   867  					OutputType: outputTypeDocLink,
   868  				}
   869  			}
   870  
   871  			name := strings.Title(tokenToName(obj.Token))
   872  			objs = append(objs, docNestedType{
   873  				Name:        wbr(name),
   874  				AnchorID:    strings.ToLower(name),
   875  				APIDocLinks: apiDocLinks,
   876  				Properties:  props,
   877  			})
   878  		}
   879  	}
   880  
   881  	sort.Slice(objs, func(i, j int) bool {
   882  		return objs[i].Name < objs[j].Name
   883  	})
   884  
   885  	return objs
   886  }
   887  
   888  // getProperties returns a slice of properties that can be rendered for docs for
   889  // the provided slice of properties in the schema.
   890  func (mod *modContext) getProperties(properties []*schema.Property, lang string, input, nested bool) []property {
   891  	if len(properties) == 0 {
   892  		return nil
   893  	}
   894  	docProperties := make([]property, 0, len(properties))
   895  	for _, prop := range properties {
   896  		if prop == nil {
   897  			continue
   898  		}
   899  
   900  		// If the property has a const value, then don't show it as an input property.
   901  		// Even though it is a valid property, it is used by the language code gen to
   902  		// generate the appropriate defaults for it. These cannot be overridden by users.
   903  		if prop.ConstValue != nil {
   904  			continue
   905  		}
   906  
   907  		characteristics := propertyCharacteristics{
   908  			input:    input,
   909  			optional: !prop.IsRequired,
   910  		}
   911  
   912  		langDocHelper := getLanguageDocHelper(lang)
   913  		name, err := langDocHelper.GetPropertyName(prop)
   914  		if err != nil {
   915  			panic(err)
   916  		}
   917  		propLangName := name
   918  
   919  		propID := strings.ToLower(propLangName + propertyLangSeparator + lang)
   920  
   921  		propTypes := make([]propertyType, 0)
   922  		if typ, isUnion := prop.Type.(*schema.UnionType); isUnion {
   923  			for _, elementType := range typ.ElementTypes {
   924  				propTypes = append(propTypes, mod.typeString(elementType, lang, characteristics, true))
   925  			}
   926  		} else {
   927  			propTypes = append(propTypes, mod.typeString(prop.Type, lang, characteristics, true))
   928  		}
   929  
   930  		docProperties = append(docProperties, property{
   931  			ID:                 propID,
   932  			DisplayName:        wbr(propLangName),
   933  			Name:               propLangName,
   934  			Comment:            prop.Comment,
   935  			DeprecationMessage: prop.DeprecationMessage,
   936  			IsRequired:         prop.IsRequired,
   937  			IsInput:            input,
   938  			Link:               "#" + propID,
   939  			Types:              propTypes,
   940  		})
   941  	}
   942  
   943  	// Sort required props to move them to the top of the properties list, then by name.
   944  	sort.SliceStable(docProperties, func(i, j int) bool {
   945  		pi, pj := docProperties[i], docProperties[j]
   946  		switch {
   947  		case pi.IsRequired != pj.IsRequired:
   948  			return pi.IsRequired && !pj.IsRequired
   949  		default:
   950  			return pi.Name < pj.Name
   951  		}
   952  	})
   953  
   954  	return docProperties
   955  }
   956  
   957  func getDockerImagePythonFormalParams() []formalParam {
   958  	return []formalParam{
   959  		{
   960  			Name: "image_name",
   961  		},
   962  		{
   963  			Name: "build",
   964  		},
   965  		{
   966  			Name:         "local_image_name",
   967  			DefaultValue: "=None",
   968  		},
   969  		{
   970  			Name:         "registry",
   971  			DefaultValue: "=None",
   972  		},
   973  		{
   974  			Name:         "skip_push",
   975  			DefaultValue: "=None",
   976  		},
   977  		{
   978  			Name:         "opts",
   979  			DefaultValue: "=None",
   980  		},
   981  	}
   982  }
   983  
   984  // Returns the rendered HTML for the resource's constructor, as well as the specific arguments.
   985  func (mod *modContext) genConstructors(r *schema.Resource, allOptionalInputs bool) (map[string]string, map[string][]formalParam) {
   986  	renderedParams := make(map[string]string)
   987  	formalParams := make(map[string][]formalParam)
   988  
   989  	for _, lang := range supportedLanguages {
   990  		var (
   991  			paramTemplate string
   992  			params        []formalParam
   993  		)
   994  		b := &bytes.Buffer{}
   995  
   996  		switch lang {
   997  		case "nodejs":
   998  			params = mod.genConstructorTS(r, allOptionalInputs)
   999  			paramTemplate = "ts_formal_param"
  1000  		case "go":
  1001  			params = mod.genConstructorGo(r, allOptionalInputs)
  1002  			paramTemplate = "go_formal_param"
  1003  		case "csharp":
  1004  			params = mod.genConstructorCS(r, allOptionalInputs)
  1005  			paramTemplate = "csharp_formal_param"
  1006  		case "python":
  1007  			params = mod.genConstructorPython(r, allOptionalInputs)
  1008  			paramTemplate = "py_formal_param"
  1009  		}
  1010  
  1011  		for i, p := range params {
  1012  			if i != 0 {
  1013  				if err := templates.ExecuteTemplate(b, "param_separator", nil); err != nil {
  1014  					panic(err)
  1015  				}
  1016  			}
  1017  			if err := templates.ExecuteTemplate(b, paramTemplate, p); err != nil {
  1018  				panic(err)
  1019  			}
  1020  		}
  1021  		renderedParams[lang] = b.String()
  1022  		formalParams[lang] = params
  1023  	}
  1024  
  1025  	return renderedParams, formalParams
  1026  }
  1027  
  1028  // getConstructorResourceInfo returns a map of per-language information about
  1029  // the resource being constructed.
  1030  func (mod *modContext) getConstructorResourceInfo(resourceTypeName string) map[string]propertyType {
  1031  	resourceMap := make(map[string]propertyType)
  1032  	resourceDisplayName := resourceTypeName
  1033  
  1034  	for _, lang := range supportedLanguages {
  1035  		// Use the module to package lookup to transform the module name to its normalized package name.
  1036  		modName := mod.getLanguageModuleName(lang)
  1037  		// Reset the type name back to the display name.
  1038  		resourceTypeName = resourceDisplayName
  1039  
  1040  		docLangHelper := getLanguageDocHelper(lang)
  1041  		switch lang {
  1042  		case "nodejs", "go", "python":
  1043  			// Intentionally left blank.
  1044  		case "csharp":
  1045  			namespace := title(mod.pkg.Name, lang)
  1046  			if ns, ok := csharpPkgInfo.Namespaces[mod.pkg.Name]; ok {
  1047  				namespace = ns
  1048  			}
  1049  			if mod.mod == "" {
  1050  				resourceTypeName = fmt.Sprintf("Pulumi.%s.%s", namespace, resourceTypeName)
  1051  				break
  1052  			}
  1053  
  1054  			resourceTypeName = fmt.Sprintf("Pulumi.%s.%s.%s", namespace, modName, resourceTypeName)
  1055  		default:
  1056  			panic(errors.Errorf("cannot generate constructor info for unhandled language %q", lang))
  1057  		}
  1058  
  1059  		parts := strings.Split(resourceTypeName, ".")
  1060  		displayName := parts[len(parts)-1]
  1061  
  1062  		var link string
  1063  		if mod.emitAPILinks {
  1064  			link = docLangHelper.GetDocLinkForResourceType(mod.pkg, modName, resourceTypeName)
  1065  		}
  1066  
  1067  		resourceMap[lang] = propertyType{
  1068  			Name:        resourceDisplayName,
  1069  			DisplayName: displayName,
  1070  			Link:        link,
  1071  		}
  1072  	}
  1073  
  1074  	return resourceMap
  1075  }
  1076  
  1077  func (mod *modContext) getTSLookupParams(r *schema.Resource, stateParam string) []formalParam {
  1078  	docLangHelper := getLanguageDocHelper("nodejs")
  1079  	// Use the NodeJS module to package lookup to transform the module name to its normalized package name.
  1080  	modName := mod.getLanguageModuleName("nodejs")
  1081  
  1082  	var stateLink string
  1083  	if mod.emitAPILinks {
  1084  		stateLink = docLangHelper.GetDocLinkForResourceType(mod.pkg, modName, stateParam)
  1085  	}
  1086  
  1087  	return []formalParam{
  1088  		{
  1089  			Name: "name",
  1090  
  1091  			Type: propertyType{
  1092  				Name: "string",
  1093  				Link: docLangHelper.GetDocLinkForBuiltInType("string"),
  1094  			},
  1095  		},
  1096  		{
  1097  			Name: "id",
  1098  			Type: propertyType{
  1099  				Name: "Input<ID>",
  1100  				Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, "ID"),
  1101  			},
  1102  		},
  1103  		{
  1104  			Name:         "state",
  1105  			OptionalFlag: "?",
  1106  			Type: propertyType{
  1107  				Name: stateParam,
  1108  				Link: stateLink,
  1109  			},
  1110  		},
  1111  		{
  1112  			Name:         "opts",
  1113  			OptionalFlag: "?",
  1114  			Type: propertyType{
  1115  				Name: "CustomResourceOptions",
  1116  				Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, "CustomResourceOptions"),
  1117  			},
  1118  		},
  1119  	}
  1120  }
  1121  
  1122  func (mod *modContext) getGoLookupParams(r *schema.Resource, stateParam string) []formalParam {
  1123  	docLangHelper := getLanguageDocHelper("go")
  1124  	// Use the Go module to package lookup to transform the module name to its normalized package name.
  1125  	modName := mod.getLanguageModuleName("go")
  1126  
  1127  	var stateLink string
  1128  	if mod.emitAPILinks {
  1129  		stateLink = docLangHelper.GetDocLinkForResourceType(mod.pkg, modName, stateParam)
  1130  	}
  1131  
  1132  	return []formalParam{
  1133  		{
  1134  			Name:         "ctx",
  1135  			OptionalFlag: "*",
  1136  			Type: propertyType{
  1137  				Name: "Context",
  1138  				Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, "Context"),
  1139  			},
  1140  		},
  1141  		{
  1142  			Name: "name",
  1143  			Type: propertyType{
  1144  				Name: "string",
  1145  				Link: docLangHelper.GetDocLinkForBuiltInType("string"),
  1146  			},
  1147  		},
  1148  		{
  1149  			Name: "id",
  1150  			Type: propertyType{
  1151  				Name: "IDInput",
  1152  				Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, "IDInput"),
  1153  			},
  1154  		},
  1155  		{
  1156  			Name:         "state",
  1157  			OptionalFlag: "*",
  1158  			Type: propertyType{
  1159  				Name: stateParam,
  1160  				Link: stateLink,
  1161  			},
  1162  		},
  1163  		{
  1164  			Name:         "opts",
  1165  			OptionalFlag: "...",
  1166  			Type: propertyType{
  1167  				Name: "ResourceOption",
  1168  				Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, "ResourceOption"),
  1169  			},
  1170  		},
  1171  	}
  1172  }
  1173  
  1174  func (mod *modContext) getCSLookupParams(r *schema.Resource, stateParam string) []formalParam {
  1175  	modName := mod.getLanguageModuleName("csharp")
  1176  	namespace := title(mod.pkg.Name, "csharp")
  1177  	if ns, ok := csharpPkgInfo.Namespaces[mod.pkg.Name]; ok {
  1178  		namespace = ns
  1179  	}
  1180  	stateParamFQDN := fmt.Sprintf("Pulumi.%s.%s.%s", namespace, modName, stateParam)
  1181  
  1182  	docLangHelper := getLanguageDocHelper("csharp")
  1183  
  1184  	var stateLink string
  1185  	if mod.emitAPILinks {
  1186  		stateLink = docLangHelper.GetDocLinkForResourceType(mod.pkg, "", stateParamFQDN)
  1187  	}
  1188  
  1189  	return []formalParam{
  1190  		{
  1191  			Name: "name",
  1192  			Type: propertyType{
  1193  				Name: "string",
  1194  				Link: docLangHelper.GetDocLinkForBuiltInType("string"),
  1195  			},
  1196  		},
  1197  		{
  1198  			Name: "id",
  1199  			Type: propertyType{
  1200  				Name: "Input<string>",
  1201  				Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, "Pulumi.Input"),
  1202  			},
  1203  		},
  1204  		{
  1205  			Name:         "state",
  1206  			OptionalFlag: "?",
  1207  			Type: propertyType{
  1208  				Name: stateParam,
  1209  				Link: stateLink,
  1210  			},
  1211  		},
  1212  		{
  1213  			Name:         "opts",
  1214  			OptionalFlag: "?",
  1215  			DefaultValue: " = null",
  1216  			Type: propertyType{
  1217  				Name: "CustomResourceOptions",
  1218  				Link: docLangHelper.GetDocLinkForPulumiType(mod.pkg, "Pulumi.CustomResourceOptions"),
  1219  			},
  1220  		},
  1221  	}
  1222  }
  1223  
  1224  func (mod *modContext) getPythonLookupParams(r *schema.Resource, stateParam string) []formalParam {
  1225  	// The input properties for a resource needs to be exploded as
  1226  	// individual constructor params.
  1227  	docLanguageHelper := getLanguageDocHelper("python")
  1228  	params := make([]formalParam, 0, len(r.StateInputs.Properties))
  1229  	for _, p := range r.StateInputs.Properties {
  1230  		typ := docLanguageHelper.GetLanguageTypeString(mod.pkg, mod.mod, p.Type, true /*input*/, false /*optional*/)
  1231  		params = append(params, formalParam{
  1232  			Name:         python.PyName(p.Name),
  1233  			DefaultValue: " = None",
  1234  			Type: propertyType{
  1235  				Name: fmt.Sprintf("Optional[%s]", typ),
  1236  			},
  1237  		})
  1238  	}
  1239  	return params
  1240  }
  1241  
  1242  // genLookupParams generates a map of per-language way of rendering the formal parameters of the lookup function
  1243  // used to lookup an existing resource.
  1244  func (mod *modContext) genLookupParams(r *schema.Resource, stateParam string) map[string]string {
  1245  	lookupParams := make(map[string]string)
  1246  	if r.StateInputs == nil {
  1247  		return lookupParams
  1248  	}
  1249  
  1250  	for _, lang := range supportedLanguages {
  1251  		var (
  1252  			paramTemplate string
  1253  			params        []formalParam
  1254  		)
  1255  		b := &bytes.Buffer{}
  1256  
  1257  		switch lang {
  1258  		case "nodejs":
  1259  			params = mod.getTSLookupParams(r, stateParam)
  1260  			paramTemplate = "ts_formal_param"
  1261  		case "go":
  1262  			params = mod.getGoLookupParams(r, stateParam)
  1263  			paramTemplate = "go_formal_param"
  1264  		case "csharp":
  1265  			params = mod.getCSLookupParams(r, stateParam)
  1266  			paramTemplate = "csharp_formal_param"
  1267  		case "python":
  1268  			params = mod.getPythonLookupParams(r, stateParam)
  1269  			paramTemplate = "py_formal_param"
  1270  		}
  1271  
  1272  		n := len(params)
  1273  		for i, p := range params {
  1274  			if err := templates.ExecuteTemplate(b, paramTemplate, p); err != nil {
  1275  				panic(err)
  1276  			}
  1277  			if i != n-1 {
  1278  				if err := templates.ExecuteTemplate(b, "param_separator", nil); err != nil {
  1279  					panic(err)
  1280  				}
  1281  			}
  1282  		}
  1283  		lookupParams[lang] = b.String()
  1284  	}
  1285  	return lookupParams
  1286  }
  1287  
  1288  // filterOutputProperties removes the input properties from the output properties list
  1289  // (since input props are implicitly output props), returning only "output" props.
  1290  func filterOutputProperties(inputProps []*schema.Property, props []*schema.Property) []*schema.Property {
  1291  	var outputProps []*schema.Property
  1292  	inputMap := make(map[string]bool, len(inputProps))
  1293  	for _, p := range inputProps {
  1294  		inputMap[p.Name] = true
  1295  	}
  1296  	for _, p := range props {
  1297  		if _, found := inputMap[p.Name]; !found {
  1298  			outputProps = append(outputProps, p)
  1299  		}
  1300  	}
  1301  	return outputProps
  1302  }
  1303  
  1304  func (mod *modContext) genResourceHeader(r *schema.Resource) header {
  1305  	packageName := formatTitleText(mod.pkg.Name)
  1306  	resourceName := resourceName(r)
  1307  	var baseDescription string
  1308  	var titleTag string
  1309  	if mod.mod == "" {
  1310  		baseDescription = fmt.Sprintf("Explore the %s resource of the %s package, "+
  1311  			"including examples, input properties, output properties, "+
  1312  			"lookup functions, and supporting types.", resourceName, packageName)
  1313  		titleTag = fmt.Sprintf("Resource %s | Package %s", resourceName, packageName)
  1314  	} else {
  1315  		baseDescription = fmt.Sprintf("Explore the %s resource of the %s module, "+
  1316  			"including examples, input properties, output properties, "+
  1317  			"lookup functions, and supporting types.", resourceName, mod.mod)
  1318  		titleTag = fmt.Sprintf("%s.%s.%s", mod.pkg.Name, mod.mod, resourceName)
  1319  	}
  1320  
  1321  	return header{
  1322  		Title:    resourceName,
  1323  		TitleTag: titleTag,
  1324  		MetaDesc: baseDescription + " " + metaDescriptionRegexp.FindString(r.Comment),
  1325  	}
  1326  }
  1327  
  1328  // genResource is the entrypoint for generating a doc for a resource
  1329  // from its Pulumi schema.
  1330  func (mod *modContext) genResource(r *schema.Resource) resourceDocArgs {
  1331  	// Create a resource module file into which all of this resource's types will go.
  1332  	name := resourceName(r)
  1333  
  1334  	inputProps := make(map[string][]property)
  1335  	outputProps := make(map[string][]property)
  1336  	stateInputs := make(map[string][]property)
  1337  
  1338  	var filteredOutputProps []*schema.Property
  1339  	// Provider resources do not have output properties, so there won't be anything to filter.
  1340  	if !r.IsProvider {
  1341  		filteredOutputProps = filterOutputProperties(r.InputProperties, r.Properties)
  1342  	}
  1343  
  1344  	// All resources have an implicit `id` output property, that we must inject into the docs.
  1345  	filteredOutputProps = append(filteredOutputProps, &schema.Property{
  1346  		Name:       "id",
  1347  		Comment:    "The provider-assigned unique ID for this managed resource.",
  1348  		Type:       schema.StringType,
  1349  		IsRequired: true,
  1350  	})
  1351  
  1352  	for _, lang := range supportedLanguages {
  1353  		inputProps[lang] = mod.getProperties(r.InputProperties, lang, true, false)
  1354  		outputProps[lang] = mod.getProperties(filteredOutputProps, lang, false, false)
  1355  		if r.IsProvider {
  1356  			continue
  1357  		}
  1358  		if r.StateInputs != nil {
  1359  			stateProps := mod.getProperties(r.StateInputs.Properties, lang, true, false)
  1360  			for i := 0; i < len(stateProps); i++ {
  1361  				id := "state_" + stateProps[i].ID
  1362  				stateProps[i].ID = id
  1363  				stateProps[i].Link = "#" + id
  1364  			}
  1365  			stateInputs[lang] = stateProps
  1366  		}
  1367  	}
  1368  
  1369  	allOptionalInputs := true
  1370  	for _, prop := range r.InputProperties {
  1371  		// If at least one prop is required, then break.
  1372  		if prop.IsRequired {
  1373  			allOptionalInputs = false
  1374  			break
  1375  		}
  1376  	}
  1377  
  1378  	packageDetails := packageDetails{
  1379  		Repository: mod.pkg.Repository,
  1380  		License:    mod.pkg.License,
  1381  		Notes:      mod.pkg.Attribution,
  1382  	}
  1383  
  1384  	renderedCtorParams, typedCtorParams := mod.genConstructors(r, allOptionalInputs)
  1385  
  1386  	stateParam := name + "State"
  1387  
  1388  	docInfo := decomposeDocstring(r.Comment)
  1389  	data := resourceDocArgs{
  1390  		Header: mod.genResourceHeader(r),
  1391  
  1392  		Tool: mod.tool,
  1393  
  1394  		Comment:            docInfo.description,
  1395  		DeprecationMessage: r.DeprecationMessage,
  1396  		ExamplesSection:    docInfo.examples,
  1397  		ImportDocs:         docInfo.importDetails,
  1398  
  1399  		ConstructorParams:      renderedCtorParams,
  1400  		ConstructorParamsTyped: typedCtorParams,
  1401  
  1402  		ConstructorResource: mod.getConstructorResourceInfo(name),
  1403  		ArgsRequired:        !allOptionalInputs,
  1404  
  1405  		InputProperties:  inputProps,
  1406  		OutputProperties: outputProps,
  1407  		LookupParams:     mod.genLookupParams(r, stateParam),
  1408  		StateInputs:      stateInputs,
  1409  		StateParam:       stateParam,
  1410  		NestedTypes:      mod.genNestedTypes(r, true /*resourceType*/),
  1411  
  1412  		PackageDetails: packageDetails,
  1413  	}
  1414  
  1415  	return data
  1416  }
  1417  
  1418  func (mod *modContext) getNestedTypes(t schema.Type, types nestedTypeUsageInfo, input bool) {
  1419  	switch t := t.(type) {
  1420  	case *schema.ArrayType:
  1421  		glog.V(4).Infof("visiting array %s\n", t.ElementType.String())
  1422  		skip := false
  1423  		if o, ok := t.ElementType.(*schema.ObjectType); ok && types.contains(o.Token, input) {
  1424  			glog.V(4).Infof("already added %s. skipping...\n", o.Token)
  1425  			skip = true
  1426  		}
  1427  
  1428  		if !skip {
  1429  			mod.getNestedTypes(t.ElementType, types, input)
  1430  		}
  1431  	case *schema.MapType:
  1432  		glog.V(4).Infof("visiting map %s\n", t.ElementType.String())
  1433  		skip := false
  1434  		if o, ok := t.ElementType.(*schema.ObjectType); ok && types.contains(o.Token, input) {
  1435  			glog.V(4).Infof("already added %s. skipping...\n", o.Token)
  1436  			skip = true
  1437  		}
  1438  
  1439  		if !skip {
  1440  			mod.getNestedTypes(t.ElementType, types, input)
  1441  		}
  1442  	case *schema.ObjectType:
  1443  		glog.V(4).Infof("visiting object %s\n", t.Token)
  1444  		types.add(t.Token, input)
  1445  		for _, p := range t.Properties {
  1446  			if o, ok := p.Type.(*schema.ObjectType); ok && types.contains(o.Token, input) {
  1447  				glog.V(4).Infof("already added %s. skipping...\n", o.Token)
  1448  				continue
  1449  			}
  1450  			glog.V(4).Infof("visiting object property %s\n", p.Type.String())
  1451  			mod.getNestedTypes(p.Type, types, input)
  1452  		}
  1453  	case *schema.UnionType:
  1454  		glog.V(4).Infof("visiting union type %s\n", t.String())
  1455  		for _, e := range t.ElementTypes {
  1456  			if o, ok := e.(*schema.ObjectType); ok && types.contains(o.Token, input) {
  1457  				glog.V(4).Infof("already added %s. skipping...\n", o.Token)
  1458  				continue
  1459  			}
  1460  			glog.V(4).Infof("visiting union element type %s\n", e.String())
  1461  			mod.getNestedTypes(e, types, input)
  1462  		}
  1463  	}
  1464  }
  1465  
  1466  func (mod *modContext) getTypes(member interface{}, types nestedTypeUsageInfo) {
  1467  	glog.V(3).Infoln("getting nested types for module", mod.mod)
  1468  
  1469  	switch t := member.(type) {
  1470  	case *schema.ObjectType:
  1471  		for _, p := range t.Properties {
  1472  			mod.getNestedTypes(p.Type, types, false)
  1473  		}
  1474  	case *schema.Resource:
  1475  		for _, p := range t.Properties {
  1476  			mod.getNestedTypes(p.Type, types, false)
  1477  		}
  1478  		for _, p := range t.InputProperties {
  1479  			mod.getNestedTypes(p.Type, types, true)
  1480  		}
  1481  	case *schema.Function:
  1482  		if t.Inputs != nil {
  1483  			mod.getNestedTypes(t.Inputs, types, true)
  1484  		}
  1485  		if t.Outputs != nil {
  1486  			mod.getNestedTypes(t.Outputs, types, false)
  1487  		}
  1488  	}
  1489  }
  1490  
  1491  type fs map[string][]byte
  1492  
  1493  func (fs fs) add(path string, contents []byte) {
  1494  	_, has := fs[path]
  1495  	contract.Assertf(!has, "duplicate file: %s", path)
  1496  	fs[path] = contents
  1497  }
  1498  
  1499  // getModuleFileName returns the file name to use for a module.
  1500  func (mod *modContext) getModuleFileName() string {
  1501  	if !isKubernetesPackage(mod.pkg) {
  1502  		return mod.mod
  1503  	}
  1504  
  1505  	// For k8s packages, use the Go-language info to get the file name
  1506  	// for the module.
  1507  	if override, ok := goPkgInfo.ModuleToPackage[mod.mod]; ok {
  1508  		return override
  1509  	}
  1510  	return mod.mod
  1511  }
  1512  
  1513  func (mod *modContext) gen(fs fs) error {
  1514  	modName := mod.getModuleFileName()
  1515  	var files []string
  1516  	for p := range fs {
  1517  		d := path.Dir(p)
  1518  		if d == "." {
  1519  			d = ""
  1520  		}
  1521  		if d == modName {
  1522  			files = append(files, p)
  1523  		}
  1524  	}
  1525  
  1526  	addFile := func(name, contents string) {
  1527  		p := path.Join(modName, name)
  1528  		files = append(files, p)
  1529  		fs.add(p, []byte(contents))
  1530  	}
  1531  
  1532  	// Resources
  1533  	for _, r := range mod.resources {
  1534  		data := mod.genResource(r)
  1535  
  1536  		title := resourceName(r)
  1537  		buffer := &bytes.Buffer{}
  1538  
  1539  		err := templates.ExecuteTemplate(buffer, "resource.tmpl", data)
  1540  		if err != nil {
  1541  			return err
  1542  		}
  1543  
  1544  		addFile(strings.ToLower(title)+".md", buffer.String())
  1545  	}
  1546  
  1547  	// Functions
  1548  	for _, f := range mod.functions {
  1549  		data := mod.genFunction(f)
  1550  
  1551  		buffer := &bytes.Buffer{}
  1552  		err := templates.ExecuteTemplate(buffer, "function.tmpl", data)
  1553  		if err != nil {
  1554  			return err
  1555  		}
  1556  
  1557  		addFile(strings.ToLower(tokenToName(f.Token))+".md", buffer.String())
  1558  	}
  1559  
  1560  	// Generate the index files.
  1561  	idxData := mod.genIndex()
  1562  	buffer := &bytes.Buffer{}
  1563  	err := templates.ExecuteTemplate(buffer, "index.tmpl", idxData)
  1564  	if err != nil {
  1565  		return err
  1566  	}
  1567  
  1568  	fs.add(path.Join(modName, "_index.md"), []byte(buffer.String()))
  1569  	return nil
  1570  }
  1571  
  1572  // indexEntry represents an individual entry on an index page.
  1573  type indexEntry struct {
  1574  	Link        string
  1575  	DisplayName string
  1576  }
  1577  
  1578  // indexData represents the index file data to be rendered as _index.md.
  1579  type indexData struct {
  1580  	Tool string
  1581  
  1582  	Title              string
  1583  	TitleTag           string
  1584  	PackageDescription string
  1585  	// Menu indicates if an index page should be part of the TOC menu.
  1586  	Menu bool
  1587  
  1588  	LanguageLinks  map[string]string
  1589  	Functions      []indexEntry
  1590  	Resources      []indexEntry
  1591  	Modules        []indexEntry
  1592  	PackageDetails packageDetails
  1593  }
  1594  
  1595  // indexEntrySorter implements the sort.Interface for sorting
  1596  // a slice of indexEntry struct types.
  1597  type indexEntrySorter struct {
  1598  	entries []indexEntry
  1599  }
  1600  
  1601  // Len is part of sort.Interface. Returns the length of the
  1602  // entries slice.
  1603  func (s *indexEntrySorter) Len() int {
  1604  	return len(s.entries)
  1605  }
  1606  
  1607  // Swap is part of sort.Interface.
  1608  func (s *indexEntrySorter) Swap(i, j int) {
  1609  	s.entries[i], s.entries[j] = s.entries[j], s.entries[i]
  1610  }
  1611  
  1612  // Less is part of sort.Interface. It sorts the entries by their
  1613  // display name in an ascending order.
  1614  func (s *indexEntrySorter) Less(i, j int) bool {
  1615  	return s.entries[i].DisplayName < s.entries[j].DisplayName
  1616  }
  1617  
  1618  func sortIndexEntries(entries []indexEntry) {
  1619  	if len(entries) == 0 {
  1620  		return
  1621  	}
  1622  
  1623  	sorter := &indexEntrySorter{
  1624  		entries: entries,
  1625  	}
  1626  
  1627  	sort.Sort(sorter)
  1628  }
  1629  
  1630  // getLanguageLinks returns a map of links for the current module's language-specific
  1631  // docs by language.
  1632  func (mod *modContext) getLanguageLinks() map[string]string {
  1633  	languageLinks := map[string]string{}
  1634  
  1635  	if !mod.emitAPILinks {
  1636  		return languageLinks
  1637  	}
  1638  
  1639  	isK8s := isKubernetesPackage(mod.pkg)
  1640  
  1641  	for _, lang := range supportedLanguages {
  1642  		var link string
  1643  		var title string
  1644  		var langTitle string
  1645  		modName := mod.getLanguageModuleName(lang)
  1646  
  1647  		docLangHelper := getLanguageDocHelper(lang)
  1648  		switch lang {
  1649  		case "csharp":
  1650  			langTitle = ".NET"
  1651  			if override, ok := csharpPkgInfo.Namespaces[modName]; ok {
  1652  				modName = override
  1653  			} else if !ok && isK8s {
  1654  				// For k8s if we don't find a C# namespace override, then don't
  1655  				// include a link to the module since it would lead to a 404.
  1656  				continue
  1657  			}
  1658  		case "go":
  1659  			langTitle = "Go"
  1660  		case "nodejs":
  1661  			langTitle = "Node.js"
  1662  		case "python":
  1663  			langTitle = "Python"
  1664  		default:
  1665  			panic(errors.Errorf("Unknown language %s", lang))
  1666  		}
  1667  
  1668  		title, link = docLangHelper.GetModuleDocLink(mod.pkg, modName)
  1669  		languageLinks[langTitle] = fmt.Sprintf(`<a href="%s" title="%[2]s">%[2]s</a>`, link, title)
  1670  	}
  1671  	return languageLinks
  1672  }
  1673  
  1674  // genIndex emits an _index.md file for the module.
  1675  func (mod *modContext) genIndex() indexData {
  1676  	glog.V(4).Infoln("genIndex for", mod.mod)
  1677  	modules := make([]indexEntry, 0, len(mod.children))
  1678  	resources := make([]indexEntry, 0, len(mod.resources))
  1679  	functions := make([]indexEntry, 0, len(mod.functions))
  1680  
  1681  	modName := mod.getModuleFileName()
  1682  	title := modName
  1683  	menu := false
  1684  	if title == "" {
  1685  		title = formatTitleText(mod.pkg.Name)
  1686  		// Flag top-level entries for inclusion in the table-of-contents menu.
  1687  		menu = true
  1688  	}
  1689  
  1690  	// If there are submodules, list them.
  1691  	for _, mod := range mod.children {
  1692  		modName := mod.getModuleFileName()
  1693  		parts := strings.Split(modName, "/")
  1694  		modName = parts[len(parts)-1]
  1695  		modules = append(modules, indexEntry{
  1696  			Link:        strings.ToLower(modName) + "/",
  1697  			DisplayName: modName,
  1698  		})
  1699  	}
  1700  	sortIndexEntries(modules)
  1701  
  1702  	// If there are resources in the root, list them.
  1703  	for _, r := range mod.resources {
  1704  		name := resourceName(r)
  1705  		resources = append(resources, indexEntry{
  1706  			Link:        strings.ToLower(name),
  1707  			DisplayName: name,
  1708  		})
  1709  	}
  1710  	sortIndexEntries(resources)
  1711  
  1712  	// If there are functions in the root, list them.
  1713  	for _, f := range mod.functions {
  1714  		name := tokenToName(f.Token)
  1715  		functions = append(functions, indexEntry{
  1716  			Link:        strings.ToLower(name),
  1717  			DisplayName: strings.Title(name),
  1718  		})
  1719  	}
  1720  	sortIndexEntries(functions)
  1721  
  1722  	packageDetails := packageDetails{
  1723  		Repository: mod.pkg.Repository,
  1724  		License:    mod.pkg.License,
  1725  		Notes:      mod.pkg.Attribution,
  1726  		Version:    mod.pkg.Version.String(),
  1727  	}
  1728  
  1729  	var titleTag string
  1730  	var packageDescription string
  1731  	// The same index.tmpl template is used for both top level package and module pages, if modules not present,
  1732  	// assume top level package index page when formatting title tags otherwise, if contains modules, assume modules
  1733  	// top level page when generating title tags.
  1734  	if len(modules) > 0 {
  1735  		titleTag = fmt.Sprintf("Package %s", formatTitleText(title))
  1736  	} else {
  1737  		pkgName := formatTitleText(mod.pkg.Name)
  1738  		titleTag = fmt.Sprintf("Module %s | Package %s", title, pkgName)
  1739  		packageDescription = fmt.Sprintf("Explore the resources and functions of the %s module in the %s package.", title, pkgName)
  1740  	}
  1741  
  1742  	data := indexData{
  1743  		Tool:               mod.tool,
  1744  		PackageDescription: packageDescription,
  1745  		Title:              title,
  1746  		TitleTag:           titleTag,
  1747  		Menu:               menu,
  1748  		Resources:          resources,
  1749  		Functions:          functions,
  1750  		Modules:            modules,
  1751  		PackageDetails:     packageDetails,
  1752  		LanguageLinks:      mod.getLanguageLinks(),
  1753  	}
  1754  
  1755  	// If this is the root module, write out the package description.
  1756  	if mod.mod == "" {
  1757  		data.PackageDescription = mod.pkg.Description
  1758  	}
  1759  
  1760  	return data
  1761  }
  1762  
  1763  func formatTitleText(title string) string {
  1764  	// If title not found in titleLookup map, default back to title given.
  1765  	if val, ok := titleLookup[title]; ok {
  1766  		return val
  1767  	}
  1768  	return title
  1769  }
  1770  
  1771  func getMod(pkg *schema.Package, token string, modules map[string]*modContext, tool string,
  1772  	emitAPILinks bool) *modContext {
  1773  	modName := pkg.TokenToModule(token)
  1774  	mod, ok := modules[modName]
  1775  	if !ok {
  1776  		mod = &modContext{
  1777  			pkg:          pkg,
  1778  			mod:          modName,
  1779  			tool:         tool,
  1780  			emitAPILinks: emitAPILinks,
  1781  		}
  1782  
  1783  		if modName != "" {
  1784  			parentName := path.Dir(modName)
  1785  			// If the parent name is blank, it means this is the package-level.
  1786  			if parentName == "." || parentName == "" {
  1787  				parentName = ":index:"
  1788  			} else {
  1789  				parentName = ":" + parentName + ":"
  1790  			}
  1791  			parent := getMod(pkg, parentName, modules, tool, emitAPILinks)
  1792  			parent.children = append(parent.children, mod)
  1793  		}
  1794  
  1795  		modules[modName] = mod
  1796  	}
  1797  	return mod
  1798  }
  1799  
  1800  func generatePythonPropertyCaseMaps(mod *modContext, r *schema.Resource, seenTypes codegen.Set) {
  1801  	pyLangHelper := getLanguageDocHelper("python").(*python.DocLanguageHelper)
  1802  	for _, p := range r.Properties {
  1803  		pyLangHelper.GenPropertyCaseMap(mod.pkg, mod.mod, mod.tool, p, snakeCaseToCamelCase, camelCaseToSnakeCase, seenTypes)
  1804  	}
  1805  
  1806  	for _, p := range r.InputProperties {
  1807  		pyLangHelper.GenPropertyCaseMap(mod.pkg, mod.mod, mod.tool, p, snakeCaseToCamelCase, camelCaseToSnakeCase, seenTypes)
  1808  	}
  1809  }
  1810  
  1811  func generateModulesFromSchemaPackage(tool string, pkg *schema.Package, emitAPILinks bool) map[string]*modContext {
  1812  	// Group resources, types, and functions into modules.
  1813  	modules := map[string]*modContext{}
  1814  
  1815  	// Decode language-specific info.
  1816  	if err := pkg.ImportLanguages(map[string]schema.Language{
  1817  		"go":     go_gen.Importer,
  1818  		"python": python.Importer,
  1819  		"csharp": dotnet.Importer,
  1820  		"nodejs": nodejs.Importer,
  1821  	}); err != nil {
  1822  		panic(err)
  1823  	}
  1824  	goPkgInfo, _ = pkg.Language["go"].(go_gen.GoPackageInfo)
  1825  	csharpPkgInfo, _ = pkg.Language["csharp"].(dotnet.CSharpPackageInfo)
  1826  	nodePkgInfo, _ = pkg.Language["nodejs"].(nodejs.NodePackageInfo)
  1827  	pythonPkgInfo, _ = pkg.Language["python"].(python.PackageInfo)
  1828  
  1829  	goLangHelper := getLanguageDocHelper("go").(*go_gen.DocLanguageHelper)
  1830  	// Generate the Go package map info now, so we can use that to get the type string
  1831  	// names later.
  1832  	goLangHelper.GeneratePackagesMap(pkg, tool, goPkgInfo)
  1833  
  1834  	csharpLangHelper := getLanguageDocHelper("csharp").(*dotnet.DocLanguageHelper)
  1835  	csharpLangHelper.Namespaces = csharpPkgInfo.Namespaces
  1836  
  1837  	scanResource := func(r *schema.Resource) {
  1838  		mod := getMod(pkg, r.Token, modules, tool, emitAPILinks)
  1839  		mod.resources = append(mod.resources, r)
  1840  
  1841  		generatePythonPropertyCaseMaps(mod, r, seenCasingTypes)
  1842  	}
  1843  
  1844  	scanK8SResource := func(r *schema.Resource) {
  1845  		mod := getKubernetesMod(pkg, r.Token, modules, tool)
  1846  		mod.resources = append(mod.resources, r)
  1847  	}
  1848  
  1849  	glog.V(3).Infoln("scanning resources")
  1850  	if isKubernetesPackage(pkg) {
  1851  		scanK8SResource(pkg.Provider)
  1852  		for _, r := range pkg.Resources {
  1853  			scanK8SResource(r)
  1854  		}
  1855  	} else {
  1856  		scanResource(pkg.Provider)
  1857  		for _, r := range pkg.Resources {
  1858  			scanResource(r)
  1859  		}
  1860  	}
  1861  	glog.V(3).Infoln("done scanning resources")
  1862  
  1863  	for _, f := range pkg.Functions {
  1864  		mod := getMod(pkg, f.Token, modules, tool, emitAPILinks)
  1865  		mod.functions = append(mod.functions, f)
  1866  	}
  1867  	return modules
  1868  }
  1869  
  1870  // GeneratePackage generates the docs package with docs for each resource given the Pulumi
  1871  // schema.
  1872  func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error) {
  1873  	emitAPILinks := pkg.Name != "azure-nextgen" && pkg.Name != "eks"
  1874  
  1875  	templates = template.New("").Funcs(template.FuncMap{
  1876  		"htmlSafe": func(html string) template.HTML {
  1877  			// Markdown fragments in the templates need to be rendered as-is,
  1878  			// so that html/template package doesn't try to inject data into it,
  1879  			// which will most certainly fail.
  1880  			// nolint gosec
  1881  			return template.HTML(html)
  1882  		},
  1883  		"hasDocLinksForLang": func(m map[string]apiTypeDocLinks, lang string) bool {
  1884  			if !emitAPILinks {
  1885  				return false
  1886  			}
  1887  
  1888  			_, ok := m[lang]
  1889  			return ok
  1890  		},
  1891  	})
  1892  
  1893  	for name, b := range packagedTemplates {
  1894  		template.Must(templates.New(name).Parse(string(b)))
  1895  	}
  1896  
  1897  	defer glog.Flush()
  1898  
  1899  	// Generate the modules from the schema, and for every module
  1900  	// run the generator functions to generate markdown files.
  1901  	modules := generateModulesFromSchemaPackage(tool, pkg, emitAPILinks)
  1902  	glog.V(3).Infoln("generating package now...")
  1903  	files := fs{}
  1904  	for _, mod := range modules {
  1905  		if err := mod.gen(files); err != nil {
  1906  			return nil, err
  1907  		}
  1908  	}
  1909  
  1910  	return files, nil
  1911  }