github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/codegen/dotnet/gen_program.go (about) 1 // Copyright 2016-2021, 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 package dotnet 16 17 import ( 18 "bytes" 19 "fmt" 20 "io" 21 "io/ioutil" 22 "path" 23 "strings" 24 25 "github.com/hashicorp/hcl/v2" 26 "github.com/pulumi/pulumi/pkg/v3/codegen" 27 "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model" 28 "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model/format" 29 "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax" 30 "github.com/pulumi/pulumi/pkg/v3/codegen/pcl" 31 "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 32 "github.com/pulumi/pulumi/sdk/v3/go/common/encoding" 33 "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 34 "github.com/pulumi/pulumi/sdk/v3/go/common/workspace" 35 ) 36 37 type GenerateProgramOptions struct { 38 // Determines whether ResourceArg types have an implicit name 39 // when constructing a resource. For example: 40 // when implicitResourceArgsTypeName is set to true, 41 // new Bucket("name", new BucketArgs { ... }) 42 // becomes 43 // new Bucket("name", new() { ... }); 44 // The latter syntax is only available on .NET 6 or later 45 implicitResourceArgsTypeName bool 46 } 47 48 type generator struct { 49 // The formatter to use when generating code. 50 *format.Formatter 51 program *pcl.Program 52 // C# namespace map per package. 53 namespaces map[string]map[string]string 54 // C# codegen compatibility mode per package. 55 compatibilities map[string]string 56 // A function to convert tokens to module names per package (utilizes the `moduleFormat` setting internally). 57 tokenToModules map[string]func(x string) string 58 // Type names per invoke function token. 59 functionArgs map[string]string 60 // keep track of variable identifiers which are the result of an invoke 61 // for example "var resourceGroup = GetResourceGroup.Invoke(...)" 62 // we will keep track of the reference "resourceGroup" 63 // 64 // later on when apply a traversal such as resourceGroup.name, 65 // we should rewrite it as resourceGroup.Apply(resourceGroupResult => resourceGroupResult.name) 66 functionInvokes map[string]*schema.Function 67 // Whether awaits are needed, and therefore an async Initialize method should be declared. 68 asyncInit bool 69 configCreated bool 70 diagnostics hcl.Diagnostics 71 insideFunctionInvoke bool 72 insideAwait bool 73 // Program generation options 74 generateOptions GenerateProgramOptions 75 } 76 77 const pulumiPackage = "pulumi" 78 79 func GenerateProgramWithOptions( 80 program *pcl.Program, 81 options GenerateProgramOptions) (map[string][]byte, hcl.Diagnostics, error) { 82 pcl.MapProvidersAsResources(program) 83 // Linearize the nodes into an order appropriate for procedural code generation. 84 nodes := pcl.Linearize(program) 85 86 // Import C#-specific schema info. 87 namespaces := make(map[string]map[string]string) 88 compatibilities := make(map[string]string) 89 tokenToModules := make(map[string]func(x string) string) 90 functionArgs := make(map[string]string) 91 packages, err := program.PackageSnapshots() 92 if err != nil { 93 return nil, nil, err 94 } 95 for _, p := range packages { 96 if err := p.ImportLanguages(map[string]schema.Language{"csharp": Importer}); err != nil { 97 return make(map[string][]byte), nil, err 98 } 99 100 csharpInfo, hasInfo := p.Language["csharp"].(CSharpPackageInfo) 101 if !hasInfo { 102 csharpInfo = CSharpPackageInfo{} 103 } 104 packageNamespaces := csharpInfo.Namespaces 105 namespaces[p.Name] = packageNamespaces 106 compatibilities[p.Name] = csharpInfo.Compatibility 107 tokenToModules[p.Name] = p.TokenToModule 108 109 for _, f := range p.Functions { 110 if f.Inputs != nil { 111 functionArgs[f.Inputs.Token] = f.Token 112 } 113 } 114 } 115 116 g := &generator{ 117 program: program, 118 namespaces: namespaces, 119 compatibilities: compatibilities, 120 tokenToModules: tokenToModules, 121 functionArgs: functionArgs, 122 functionInvokes: map[string]*schema.Function{}, 123 generateOptions: options, 124 } 125 126 g.Formatter = format.NewFormatter(g) 127 128 for _, n := range nodes { 129 if r, ok := n.(*pcl.Resource); ok && requiresAsyncInit(r) { 130 g.asyncInit = true 131 break 132 } 133 } 134 135 var index bytes.Buffer 136 g.genPreamble(&index, program) 137 138 g.Indented(func() { 139 for _, n := range nodes { 140 g.genNode(&index, n) 141 } 142 }) 143 g.genPostamble(&index, nodes) 144 145 files := map[string][]byte{ 146 "Program.cs": index.Bytes(), 147 } 148 return files, g.diagnostics, nil 149 } 150 151 func GenerateProgram(program *pcl.Program) (map[string][]byte, hcl.Diagnostics, error) { 152 defaultOptions := GenerateProgramOptions{ 153 // by default, we generate C# code that targets .NET 6 154 implicitResourceArgsTypeName: true, 155 } 156 157 return GenerateProgramWithOptions(program, defaultOptions) 158 } 159 160 func GenerateProject(directory string, project workspace.Project, program *pcl.Program) error { 161 files, diagnostics, err := GenerateProgram(program) 162 if err != nil { 163 return err 164 } 165 if diagnostics.HasErrors() { 166 return diagnostics 167 } 168 169 // Set the runtime to "dotnet" then marshal to Pulumi.yaml 170 project.Runtime = workspace.NewProjectRuntimeInfo("dotnet", nil) 171 projectBytes, err := encoding.YAML.Marshal(project) 172 if err != nil { 173 return err 174 } 175 files["Pulumi.yaml"] = projectBytes 176 177 // Build a .csproj based on the packages used by program 178 var csproj bytes.Buffer 179 csproj.WriteString(`<Project Sdk="Microsoft.NET.Sdk"> 180 181 <PropertyGroup> 182 <OutputType>Exe</OutputType> 183 <TargetFramework>net6.0</TargetFramework> 184 <Nullable>enable</Nullable> 185 </PropertyGroup> 186 187 <ItemGroup> 188 <PackageReference Include="Pulumi" Version="3.*" /> 189 `) 190 191 // For each package add a PackageReference line 192 packages, err := program.PackageSnapshots() 193 if err != nil { 194 return err 195 } 196 for _, p := range packages { 197 packageTemplate := " <PackageReference Include=\"%s\" Version=\"%s\" />\n" 198 199 if err := p.ImportLanguages(map[string]schema.Language{"csharp": Importer}); err != nil { 200 return err 201 } 202 if p.Name == pulumiPackage { 203 continue 204 } 205 206 packageName := fmt.Sprintf("Pulumi.%s", namespaceName(map[string]string{}, p.Name)) 207 if langInfo, found := p.Language["csharp"]; found { 208 csharpInfo, ok := langInfo.(CSharpPackageInfo) 209 if ok { 210 namespace := namespaceName(csharpInfo.Namespaces, p.Name) 211 packageName = fmt.Sprintf("%s.%s", csharpInfo.GetRootNamespace(), namespace) 212 } 213 } 214 if p.Version != nil { 215 csproj.WriteString(fmt.Sprintf(packageTemplate, packageName, p.Version.String())) 216 } else { 217 csproj.WriteString(fmt.Sprintf(packageTemplate, packageName, "*")) 218 } 219 } 220 221 csproj.WriteString(` </ItemGroup> 222 223 </Project>`) 224 225 files[project.Name.String()+".csproj"] = csproj.Bytes() 226 227 // Add the language specific .gitignore 228 files[".gitignore"] = []byte(dotnetGitIgnore) 229 230 for filename, data := range files { 231 outPath := path.Join(directory, filename) 232 err := ioutil.WriteFile(outPath, data, 0600) 233 if err != nil { 234 return fmt.Errorf("could not write output program: %w", err) 235 } 236 } 237 238 return nil 239 } 240 241 // genTrivia generates the list of trivia associated with a given token. 242 func (g *generator) genTrivia(w io.Writer, token syntax.Token) { 243 for _, t := range token.LeadingTrivia { 244 if c, ok := t.(syntax.Comment); ok { 245 g.genComment(w, c) 246 } 247 } 248 for _, t := range token.TrailingTrivia { 249 if c, ok := t.(syntax.Comment); ok { 250 g.genComment(w, c) 251 } 252 } 253 } 254 255 func (g *generator) warnf(location *hcl.Range, reason string, args ...interface{}) { 256 g.diagnostics = append(g.diagnostics, &hcl.Diagnostic{ 257 Severity: hcl.DiagWarning, 258 Summary: fmt.Sprintf(reason, args...), 259 Subject: location, 260 }) 261 } 262 263 func (g *generator) findFunctionSchema(function string, location *hcl.Range) (*schema.Function, bool) { 264 function = LowerCamelCase(function) 265 for _, pkg := range g.program.PackageReferences() { 266 for it := pkg.Functions().Range(); it.Next(); { 267 if strings.HasSuffix(it.Token(), function) { 268 fn, err := it.Function() 269 270 if err != nil { 271 g.warnf(location, "Could not find function schema for '%s'; err %s", function, err.Error()) 272 return nil, false 273 } 274 275 return fn, true 276 } 277 } 278 } 279 280 return nil, false 281 } 282 283 func (g *generator) isFunctionInvoke(localVariable *pcl.LocalVariable) (*schema.Function, bool) { 284 value := localVariable.Definition.Value 285 switch value.(type) { 286 case *model.FunctionCallExpression: 287 call := value.(*model.FunctionCallExpression) 288 switch call.Name { 289 case pcl.Invoke: 290 args := call.Args[0] 291 _, fullFunctionName := g.functionName(args) 292 functionNameParts := strings.Split(fullFunctionName, ".") 293 functionName := functionNameParts[len(functionNameParts)-1] 294 location := value.SyntaxNode().Range().Ptr() 295 return g.findFunctionSchema(functionName, location) 296 } 297 } 298 299 return nil, false 300 } 301 302 // genComment generates a comment into the output. 303 func (g *generator) genComment(w io.Writer, comment syntax.Comment) { 304 for _, l := range comment.Lines { 305 g.Fgenf(w, "%s//%s\n", g.Indent, l) 306 } 307 } 308 309 // genPreamble generates using statements, class definition and constructor. 310 func (g *generator) genPreamble(w io.Writer, program *pcl.Program) { 311 // Accumulate other using statements for the various providers and packages. Don't emit them yet, as we need 312 // to sort them later on. 313 systemUsings := codegen.NewStringSet("System.Collections.Generic") 314 pulumiUsings := codegen.NewStringSet() 315 preambleHelperMethods := codegen.NewStringSet() 316 for _, n := range program.Nodes { 317 if r, isResource := n.(*pcl.Resource); isResource { 318 pcl.FixupPulumiPackageTokens(r) 319 pkg, _, _, _ := r.DecomposeToken() 320 if pkg != pulumiPackage { 321 namespace := namespaceName(g.namespaces[pkg], pkg) 322 var info CSharpPackageInfo 323 if r.Schema != nil && r.Schema.Package != nil { 324 if csharpinfo, ok := r.Schema.Package.Language["csharp"].(CSharpPackageInfo); ok { 325 info = csharpinfo 326 } 327 } 328 pulumiUsings.Add(fmt.Sprintf("%s = %[2]s.%[1]s", namespace, info.GetRootNamespace())) 329 } 330 } 331 diags := n.VisitExpressions(nil, func(n model.Expression) (model.Expression, hcl.Diagnostics) { 332 if call, ok := n.(*model.FunctionCallExpression); ok { 333 for _, i := range g.genFunctionUsings(call) { 334 if strings.HasPrefix(i, "System") { 335 systemUsings.Add(i) 336 } else { 337 pulumiUsings.Add(i) 338 } 339 } 340 341 // Checking to see if this function call deserves its own dedicated helper method in the preamble 342 if helperMethodBody, ok := getHelperMethodIfNeeded(call.Name); ok { 343 preambleHelperMethods.Add(helperMethodBody) 344 } 345 } 346 if _, ok := n.(*model.SplatExpression); ok { 347 systemUsings.Add("System.Linq") 348 } 349 return n, nil 350 }) 351 contract.Assert(len(diags) == 0) 352 } 353 354 if g.asyncInit { 355 systemUsings.Add("System.Threading.Tasks") 356 } 357 358 for _, pkg := range systemUsings.SortedValues() { 359 g.Fprintf(w, "using %v;\n", pkg) 360 } 361 g.Fprintln(w, `using Pulumi;`) 362 for _, pkg := range pulumiUsings.SortedValues() { 363 g.Fprintf(w, "using %v;\n", pkg) 364 } 365 366 g.Fprint(w, "\n") 367 368 // If we collected any helper methods that should be added, write them just before the main func 369 for _, preambleHelperMethodBody := range preambleHelperMethods.SortedValues() { 370 g.Fprintf(w, "\t%s\n\n", preambleHelperMethodBody) 371 } 372 373 asyncKeywordWhenNeeded := "" 374 if g.asyncInit { 375 asyncKeywordWhenNeeded = "async" 376 } 377 g.Fprintf(w, "return await Deployment.RunAsync(%s() => \n", asyncKeywordWhenNeeded) 378 g.Fprint(w, "{\n") 379 } 380 381 // hasOutputVariables checks whether there are any output declarations 382 func hasOutputVariables(nodes []pcl.Node) bool { 383 for _, n := range nodes { 384 switch n.(type) { 385 case *pcl.OutputVariable: 386 return true 387 } 388 } 389 390 return false 391 } 392 393 // genPostamble closes the method and the class and declares stack output statements. 394 func (g *generator) genPostamble(w io.Writer, nodes []pcl.Node) { 395 if hasOutputVariables(nodes) { 396 g.Indented(func() { 397 g.Fgenf(w, "%sreturn new Dictionary<string, object?>\n", g.Indent) 398 g.Fgenf(w, "%s{\n", g.Indent) 399 g.Indented(func() { 400 // Emit stack output properties 401 for _, n := range nodes { 402 switch n := n.(type) { 403 case *pcl.OutputVariable: 404 outputID := fmt.Sprintf(`"%s"`, g.escapeString(n.LogicalName(), false, false)) 405 g.Fgenf(w, "%s[%s] = %.3v,\n", g.Indent, outputID, g.lowerExpression(n.Value, n.Type())) 406 } 407 } 408 }) 409 g.Fgenf(w, "%s};\n", g.Indent) 410 }) 411 } 412 // Close lambda call expression 413 g.Fprintf(w, "});\n\n") 414 } 415 416 func (g *generator) genNode(w io.Writer, n pcl.Node) { 417 switch n := n.(type) { 418 case *pcl.Resource: 419 g.genResource(w, n) 420 case *pcl.ConfigVariable: 421 g.genConfigVariable(w, n) 422 case *pcl.LocalVariable: 423 g.genLocalVariable(w, n) 424 } 425 } 426 427 // requiresAsyncInit returns true if the program requires awaits in the code, and therefore an asynchronous 428 // method must be declared. 429 func requiresAsyncInit(r *pcl.Resource) bool { 430 if r.Options == nil || r.Options.Range == nil { 431 return false 432 } 433 434 return model.ContainsPromises(r.Options.Range.Type()) 435 } 436 437 // resourceTypeName computes the C# class name for the given resource. 438 func (g *generator) resourceTypeName(r *pcl.Resource) string { 439 pcl.FixupPulumiPackageTokens(r) 440 // Compute the resource type from the Pulumi type token. 441 pkg, module, member, diags := r.DecomposeToken() 442 contract.Assert(len(diags) == 0) 443 444 namespaces := g.namespaces[pkg] 445 rootNamespace := namespaceName(namespaces, pkg) 446 447 namespace := namespaceName(namespaces, module) 448 namespaceTokens := strings.Split(namespace, "/") 449 for i, name := range namespaceTokens { 450 namespaceTokens[i] = Title(name) 451 } 452 namespace = strings.Join(namespaceTokens, ".") 453 454 if namespace != "" { 455 namespace = "." + namespace 456 } 457 458 qualifiedMemberName := fmt.Sprintf("%s%s.%s", rootNamespace, namespace, Title(member)) 459 return qualifiedMemberName 460 } 461 462 func (g *generator) extractInputPropertyNameMap(r *pcl.Resource) map[string]string { 463 // Extract language-specific property names from schema 464 var csharpInputPropertyNameMap = make(map[string]string) 465 if r.Schema != nil { 466 for _, inputProperty := range r.Schema.InputProperties { 467 if val1, ok := inputProperty.Language["csharp"]; ok { 468 if val2, ok := val1.(CSharpPropertyInfo); ok { 469 csharpInputPropertyNameMap[inputProperty.Name] = val2.Name 470 } 471 } 472 } 473 } 474 return csharpInputPropertyNameMap 475 } 476 477 // resourceArgsTypeName computes the C# arguments class name for the given resource. 478 func (g *generator) resourceArgsTypeName(r *pcl.Resource) string { 479 // Compute the resource type from the Pulumi type token. 480 pkg, module, member, diags := r.DecomposeToken() 481 contract.Assert(len(diags) == 0) 482 483 namespaces := g.namespaces[pkg] 484 rootNamespace := namespaceName(namespaces, pkg) 485 namespace := namespaceName(namespaces, module) 486 if g.compatibilities[pkg] == "kubernetes20" && module != "" { 487 namespace = fmt.Sprintf("Types.Inputs.%s", namespace) 488 } 489 490 if namespace != "" { 491 namespace = "." + namespace 492 } 493 494 return fmt.Sprintf("%s%s.%sArgs", rootNamespace, namespace, Title(member)) 495 } 496 497 // functionName computes the C# namespace and class name for the given function token. 498 func (g *generator) functionName(tokenArg model.Expression) (string, string) { 499 token := tokenArg.(*model.TemplateExpression).Parts[0].(*model.LiteralValueExpression).Value.AsString() 500 tokenRange := tokenArg.SyntaxNode().Range() 501 502 // Compute the resource type from the Pulumi type token. 503 pkg, module, member, diags := pcl.DecomposeToken(token, tokenRange) 504 contract.Assert(len(diags) == 0) 505 namespaces := g.namespaces[pkg] 506 rootNamespace := namespaceName(namespaces, pkg) 507 namespace := namespaceName(namespaces, module) 508 509 if namespace != "" { 510 namespace = "." + namespace 511 } 512 513 return rootNamespace, fmt.Sprintf("%s%s.%s", rootNamespace, namespace, Title(member)) 514 } 515 516 func (g *generator) toSchemaType(destType model.Type) (schema.Type, bool) { 517 schemaType, ok := pcl.GetSchemaForType(destType.(model.Type)) 518 if !ok { 519 return nil, false 520 } 521 return codegen.UnwrapType(schemaType), true 522 } 523 524 // argumentTypeName computes the C# argument class name for the given expression and model type. 525 func (g *generator) argumentTypeName(expr model.Expression, destType model.Type) string { 526 suffix := "Args" 527 if g.insideFunctionInvoke { 528 suffix = "InputArgs" 529 } 530 return g.argumentTypeNameWithSuffix(expr, destType, suffix) 531 } 532 533 func (g *generator) argumentTypeNameWithSuffix(expr model.Expression, destType model.Type, suffix string) string { 534 schemaType, ok := g.toSchemaType(destType) 535 if !ok { 536 return "" 537 } 538 539 objType, ok := schemaType.(*schema.ObjectType) 540 if !ok { 541 return "" 542 } 543 544 token := objType.Token 545 tokenRange := expr.SyntaxNode().Range() 546 qualifier := "Inputs" 547 if f, ok := g.functionArgs[token]; ok { 548 token = f 549 qualifier = "" 550 } 551 552 pkg, _, member, diags := pcl.DecomposeToken(token, tokenRange) 553 contract.Assert(len(diags) == 0) 554 module := g.tokenToModules[pkg](token) 555 namespaces := g.namespaces[pkg] 556 rootNamespace := namespaceName(namespaces, pkg) 557 namespace := namespaceName(namespaces, module) 558 if strings.ToLower(namespace) == "index" { 559 namespace = "" 560 } 561 if namespace != "" { 562 namespace = "." + namespace 563 } 564 if g.compatibilities[pkg] == "kubernetes20" { 565 namespace = ".Types.Inputs" + namespace 566 } else if qualifier != "" { 567 namespace = namespace + "." + qualifier 568 } 569 member = member + suffix 570 571 return fmt.Sprintf("%s%s.%s", rootNamespace, namespace, Title(member)) 572 } 573 574 // makeResourceName returns the expression that should be emitted for a resource's "name" parameter given its base name 575 // and the count variable name, if any. 576 func (g *generator) makeResourceName(baseName, count string) string { 577 if count == "" { 578 return fmt.Sprintf(`"%s"`, baseName) 579 } 580 return fmt.Sprintf("$\"%s-{%s}\"", baseName, count) 581 } 582 583 func (g *generator) genResourceOptions(opts *pcl.ResourceOptions) string { 584 if opts == nil { 585 return "" 586 } 587 var result bytes.Buffer 588 appendOption := func(name string, value model.Expression) { 589 if result.Len() == 0 { 590 _, err := fmt.Fprintf(&result, ", new CustomResourceOptions\n%s{", g.Indent) 591 g.Indent += " " 592 contract.IgnoreError(err) 593 } 594 595 if name == "IgnoreChanges" { 596 // ignore changes need to be special cased 597 // because new [] { "field" } cannot be implicitly casted to List<string> 598 // which is the type of IgnoreChanges 599 if changes, isTuple := value.(*model.TupleConsExpression); isTuple { 600 g.Fgenf(&result, "\n%sIgnoreChanges =", g.Indent) 601 g.Fgenf(&result, "\n%s{", g.Indent) 602 g.Indented(func() { 603 for _, v := range changes.Expressions { 604 g.Fgenf(&result, "\n%s\"%.v\",", g.Indent, v) 605 } 606 }) 607 g.Fgenf(&result, "\n%s}", g.Indent) 608 } else { 609 g.Fgenf(&result, "\n%s%s = %v,", g.Indent, name, g.lowerExpression(value, value.Type())) 610 } 611 } else { 612 g.Fgenf(&result, "\n%s%s = %v,", g.Indent, name, g.lowerExpression(value, value.Type())) 613 } 614 } 615 616 if opts.Parent != nil { 617 appendOption("Parent", opts.Parent) 618 } 619 if opts.Provider != nil { 620 appendOption("Provider", opts.Provider) 621 } 622 if opts.DependsOn != nil { 623 appendOption("DependsOn", opts.DependsOn) 624 } 625 if opts.Protect != nil { 626 appendOption("Protect", opts.Protect) 627 } 628 if opts.IgnoreChanges != nil { 629 appendOption("IgnoreChanges", opts.IgnoreChanges) 630 } 631 632 if result.Len() != 0 { 633 g.Indent = g.Indent[:len(g.Indent)-4] 634 _, err := fmt.Fprintf(&result, "\n%s}", g.Indent) 635 contract.IgnoreError(err) 636 } 637 638 return result.String() 639 } 640 641 // genResource handles the generation of instantiations of non-builtin resources. 642 func (g *generator) genResource(w io.Writer, r *pcl.Resource) { 643 qualifiedMemberName := g.resourceTypeName(r) 644 csharpInputPropertyNameMap := g.extractInputPropertyNameMap(r) 645 646 // Add conversions to input properties 647 for _, input := range r.Inputs { 648 destType, diagnostics := r.InputType.Traverse(hcl.TraverseAttr{Name: input.Name}) 649 g.diagnostics = append(g.diagnostics, diagnostics...) 650 input.Value = g.lowerExpression(input.Value, destType.(model.Type)) 651 if csharpName, ok := csharpInputPropertyNameMap[input.Name]; ok { 652 input.Name = csharpName 653 } 654 } 655 656 pcl.AnnotateResourceInputs(r) 657 658 name := r.LogicalName() 659 variableName := makeValidIdentifier(r.Name()) 660 argsName := g.resourceArgsTypeName(r) 661 g.genTrivia(w, r.Definition.Tokens.GetType("")) 662 for _, l := range r.Definition.Tokens.GetLabels(nil) { 663 g.genTrivia(w, l) 664 } 665 g.genTrivia(w, r.Definition.Tokens.GetOpenBrace()) 666 667 instantiate := func(resName string) { 668 if len(r.Inputs) == 0 && r.Options == nil { 669 // only resource name is provided 670 g.Fgenf(w, "new %s(%s)", qualifiedMemberName, resName) 671 } else { 672 if g.generateOptions.implicitResourceArgsTypeName { 673 g.Fgenf(w, "new %s(%s, new()\n", qualifiedMemberName, resName) 674 } else { 675 g.Fgenf(w, "new %s(%s, new %s\n", qualifiedMemberName, resName, argsName) 676 } 677 678 g.Fgenf(w, "%s{\n", g.Indent) 679 g.Indented(func() { 680 for _, attr := range r.Inputs { 681 g.Fgenf(w, "%s%s =", g.Indent, propertyName(attr.Name)) 682 g.Fgenf(w, " %.v,\n", attr.Value) 683 } 684 }) 685 g.Fgenf(w, "%s}%s)", g.Indent, g.genResourceOptions(r.Options)) 686 } 687 } 688 689 if r.Options != nil && r.Options.Range != nil { 690 rangeType := model.ResolveOutputs(r.Options.Range.Type()) 691 rangeExpr := g.lowerExpression(r.Options.Range, rangeType) 692 693 g.Fgenf(w, "%svar %s = new List<%s>();\n", g.Indent, variableName, qualifiedMemberName) 694 695 resKey := "Key" 696 if model.InputType(model.NumberType).ConversionFrom(rangeExpr.Type()) != model.NoConversion { 697 g.Fgenf(w, "%sfor (var rangeIndex = 0; rangeIndex < %.12o; rangeIndex++)\n", g.Indent, rangeExpr) 698 g.Fgenf(w, "%s{\n", g.Indent) 699 g.Fgenf(w, "%s var range = new { Value = rangeIndex };\n", g.Indent) 700 resKey = "Value" 701 } else { 702 rangeExpr := &model.FunctionCallExpression{ 703 Name: "entries", 704 Args: []model.Expression{rangeExpr}, 705 } 706 g.Fgenf(w, "%sforeach (var range in %.v)\n", g.Indent, rangeExpr) 707 g.Fgenf(w, "%s{\n", g.Indent) 708 } 709 710 resName := g.makeResourceName(name, "range."+resKey) 711 g.Indented(func() { 712 g.Fgenf(w, "%s%s.Add(", g.Indent, variableName) 713 instantiate(resName) 714 g.Fgenf(w, ");\n") 715 }) 716 g.Fgenf(w, "%s}\n", g.Indent) 717 } else { 718 g.Fgenf(w, "%svar %s = ", g.Indent, variableName) 719 instantiate(g.makeResourceName(name, "")) 720 g.Fgenf(w, ";\n\n") 721 } 722 723 g.genTrivia(w, r.Definition.Tokens.GetCloseBrace()) 724 } 725 726 func (g *generator) genConfigVariable(w io.Writer, v *pcl.ConfigVariable) { 727 if !g.configCreated { 728 g.Fprintf(w, "%svar config = new Config();\n", g.Indent) 729 g.configCreated = true 730 } 731 732 getType := "Object<dynamic>" 733 switch v.Type() { 734 case model.StringType: 735 getType = "" 736 case model.NumberType, model.IntType: 737 getType = "Number" 738 case model.BoolType: 739 getType = "Boolean" 740 } 741 742 getOrRequire := "Get" 743 if v.DefaultValue == nil { 744 getOrRequire = "Require" 745 } 746 747 name := makeValidIdentifier(v.Name()) 748 if v.DefaultValue != nil { 749 typ := v.DefaultValue.Type() 750 if _, ok := typ.(*model.PromiseType); ok { 751 g.Fgenf(w, "%svar %s = Output.Create(config.%s%s(\"%s\"))", 752 g.Indent, name, getOrRequire, getType, v.LogicalName()) 753 } else { 754 g.Fgenf(w, "%svar %s = config.%s%s(\"%s\")", 755 g.Indent, name, getOrRequire, getType, v.LogicalName()) 756 } 757 expr := g.lowerExpression(v.DefaultValue, v.DefaultValue.Type()) 758 g.Fgenf(w, " ?? %.v", expr) 759 } else { 760 g.Fgenf(w, "%svar %s = config.%s%s(\"%s\")", 761 g.Indent, name, getOrRequire, getType, v.LogicalName()) 762 } 763 g.Fgenf(w, ";\n") 764 } 765 766 func (g *generator) genLocalVariable(w io.Writer, localVariable *pcl.LocalVariable) { 767 variableName := makeValidIdentifier(localVariable.Name()) 768 value := localVariable.Definition.Value 769 functionSchema, isInvokeCall := g.isFunctionInvoke(localVariable) 770 if isInvokeCall { 771 result := g.lowerExpressionWithoutApplies(value, value.Type()) 772 g.functionInvokes[variableName] = functionSchema 773 g.Fgenf(w, "%svar %s = %v;\n\n", g.Indent, variableName, result) 774 } else { 775 result := g.lowerExpression(value, value.Type()) 776 g.Fgenf(w, "%svar %s = %v;\n\n", g.Indent, variableName, result) 777 } 778 } 779 780 func (g *generator) genNYI(w io.Writer, reason string, vs ...interface{}) { 781 message := fmt.Sprintf("not yet implemented: %s", fmt.Sprintf(reason, vs...)) 782 g.diagnostics = append(g.diagnostics, &hcl.Diagnostic{ 783 Severity: hcl.DiagError, 784 Summary: message, 785 Detail: message, 786 }) 787 g.Fgenf(w, "\"TODO: %s\"", fmt.Sprintf(reason, vs...)) 788 }