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

     1  package nodejs
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"math/big"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/hcl/v2"
    11  	"github.com/hashicorp/hcl/v2/hclsyntax"
    12  	"github.com/zclconf/go-cty/cty"
    13  	"github.com/zclconf/go-cty/cty/convert"
    14  
    15  	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
    16  	"github.com/pulumi/pulumi/pkg/v3/codegen/pcl"
    17  	"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
    18  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    19  )
    20  
    21  type nameInfo int
    22  
    23  func (nameInfo) Format(name string) string {
    24  	return makeValidIdentifier(name)
    25  }
    26  
    27  func (g *generator) lowerExpression(expr model.Expression, typ model.Type) model.Expression {
    28  	// TODO(pdg): diagnostics
    29  	if g.asyncMain {
    30  		expr = g.awaitInvokes(expr)
    31  	}
    32  	expr = pcl.RewritePropertyReferences(expr)
    33  	expr, diags := pcl.RewriteApplies(expr, nameInfo(0), !g.asyncMain)
    34  	if typ != nil {
    35  		var convertDiags hcl.Diagnostics
    36  		expr, convertDiags = pcl.RewriteConversions(expr, typ)
    37  		diags = diags.Extend(convertDiags)
    38  	}
    39  	expr, lowerProxyDiags := g.lowerProxyApplies(expr)
    40  	diags = diags.Extend(lowerProxyDiags)
    41  	g.diagnostics = g.diagnostics.Extend(diags)
    42  	return expr
    43  }
    44  
    45  func (g *generator) GetPrecedence(expr model.Expression) int {
    46  	// Precedence is derived from
    47  	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence.
    48  	switch expr := expr.(type) {
    49  	case *model.ConditionalExpression:
    50  		return 4
    51  	case *model.BinaryOpExpression:
    52  		switch expr.Operation {
    53  		case hclsyntax.OpLogicalOr:
    54  			return 5
    55  		case hclsyntax.OpLogicalAnd:
    56  			return 6
    57  		case hclsyntax.OpEqual, hclsyntax.OpNotEqual:
    58  			return 11
    59  		case hclsyntax.OpGreaterThan, hclsyntax.OpGreaterThanOrEqual, hclsyntax.OpLessThan,
    60  			hclsyntax.OpLessThanOrEqual:
    61  			return 12
    62  		case hclsyntax.OpAdd, hclsyntax.OpSubtract:
    63  			return 14
    64  		case hclsyntax.OpMultiply, hclsyntax.OpDivide, hclsyntax.OpModulo:
    65  			return 15
    66  		default:
    67  			contract.Failf("unexpected binary expression %v", expr)
    68  		}
    69  	case *model.UnaryOpExpression:
    70  		return 17
    71  	case *model.FunctionCallExpression:
    72  		switch expr.Name {
    73  		case intrinsicAwait:
    74  			return 17
    75  		case intrinsicInterpolate:
    76  			return 22
    77  		default:
    78  			return 20
    79  		}
    80  	case *model.ForExpression, *model.IndexExpression, *model.RelativeTraversalExpression, *model.SplatExpression,
    81  		*model.TemplateJoinExpression:
    82  		return 20
    83  	case *model.AnonymousFunctionExpression, *model.LiteralValueExpression, *model.ObjectConsExpression,
    84  		*model.ScopeTraversalExpression, *model.TemplateExpression, *model.TupleConsExpression:
    85  		return 22
    86  	default:
    87  		contract.Failf("unexpected expression %v of type %T", expr, expr)
    88  	}
    89  	return 0
    90  }
    91  
    92  func (g *generator) GenAnonymousFunctionExpression(w io.Writer, expr *model.AnonymousFunctionExpression) {
    93  	switch len(expr.Signature.Parameters) {
    94  	case 0:
    95  		g.Fgen(w, "()")
    96  	case 1:
    97  		g.Fgenf(w, "%s", expr.Signature.Parameters[0].Name)
    98  	default:
    99  		g.Fgen(w, "([")
   100  		for i, p := range expr.Signature.Parameters {
   101  			if i > 0 {
   102  				g.Fgen(w, ", ")
   103  			}
   104  			g.Fgenf(w, "%s", p.Name)
   105  		}
   106  		g.Fgen(w, "])")
   107  	}
   108  
   109  	g.Fgenf(w, " => %.v", expr.Body)
   110  }
   111  
   112  func (g *generator) GenBinaryOpExpression(w io.Writer, expr *model.BinaryOpExpression) {
   113  	opstr, precedence := "", g.GetPrecedence(expr)
   114  	switch expr.Operation {
   115  	case hclsyntax.OpAdd:
   116  		opstr = "+"
   117  	case hclsyntax.OpDivide:
   118  		opstr = "/"
   119  	case hclsyntax.OpEqual:
   120  		opstr = "=="
   121  	case hclsyntax.OpGreaterThan:
   122  		opstr = ">"
   123  	case hclsyntax.OpGreaterThanOrEqual:
   124  		opstr = ">="
   125  	case hclsyntax.OpLessThan:
   126  		opstr = "<"
   127  	case hclsyntax.OpLessThanOrEqual:
   128  		opstr = "<="
   129  	case hclsyntax.OpLogicalAnd:
   130  		opstr = "&&"
   131  	case hclsyntax.OpLogicalOr:
   132  		opstr = "||"
   133  	case hclsyntax.OpModulo:
   134  		opstr = "%"
   135  	case hclsyntax.OpMultiply:
   136  		opstr = "*"
   137  	case hclsyntax.OpNotEqual:
   138  		opstr = "!="
   139  	case hclsyntax.OpSubtract:
   140  		opstr = "-"
   141  	default:
   142  		opstr, precedence = ",", 1
   143  	}
   144  
   145  	g.Fgenf(w, "%.[1]*[2]v %[3]v %.[1]*[4]o", precedence, expr.LeftOperand, opstr, expr.RightOperand)
   146  }
   147  
   148  func (g *generator) GenConditionalExpression(w io.Writer, expr *model.ConditionalExpression) {
   149  	g.Fgenf(w, "%.4v ? %.4v : %.4v", expr.Condition, expr.TrueResult, expr.FalseResult)
   150  }
   151  
   152  func (g *generator) GenForExpression(w io.Writer, expr *model.ForExpression) {
   153  	switch expr.Collection.Type().(type) {
   154  	case *model.ListType, *model.TupleType:
   155  		if expr.KeyVariable == nil {
   156  			g.Fgenf(w, "%.20v", expr.Collection)
   157  		} else {
   158  			g.Fgenf(w, "%.20v.map((v, k) => [k, v])", expr.Collection)
   159  		}
   160  	case *model.MapType, *model.ObjectType:
   161  		if expr.KeyVariable == nil {
   162  			g.Fgenf(w, "Object.values(%.v)", expr.Collection)
   163  		} else {
   164  			g.Fgenf(w, "Object.entries(%.v)", expr.Collection)
   165  		}
   166  	}
   167  
   168  	fnParams, reduceParams := expr.ValueVariable.Name, expr.ValueVariable.Name
   169  	if expr.KeyVariable != nil {
   170  		reduceParams = fmt.Sprintf("[%.v, %.v]", expr.KeyVariable.Name, expr.ValueVariable.Name)
   171  		fnParams = fmt.Sprintf("(%v)", reduceParams)
   172  	}
   173  
   174  	if expr.Condition != nil {
   175  		g.Fgenf(w, ".filter(%s => %.v)", fnParams, expr.Condition)
   176  	}
   177  
   178  	if expr.Key != nil {
   179  		// TODO(pdg): grouping
   180  		g.Fgenf(w, ".reduce((__obj, %s) => { ...__obj, [%.v]: %.v })", reduceParams, expr.Key, expr.Value)
   181  	} else {
   182  		g.Fgenf(w, ".map(%s => %.v)", fnParams, expr.Value)
   183  	}
   184  }
   185  
   186  func (g *generator) genApply(w io.Writer, expr *model.FunctionCallExpression) {
   187  	// Extract the list of outputs and the continuation expression from the `__apply` arguments.
   188  	applyArgs, then := pcl.ParseApplyCall(expr)
   189  
   190  	// If all of the arguments are promises, use promise methods. If any argument is an output, convert all other args
   191  	// to outputs and use output methods.
   192  	anyOutputs := false
   193  	for _, arg := range applyArgs {
   194  		if isOutputType(arg.Type()) {
   195  			anyOutputs = true
   196  		}
   197  	}
   198  
   199  	apply, all := "then", "Promise.all"
   200  	if anyOutputs {
   201  		apply, all = "apply", "pulumi.all"
   202  	}
   203  
   204  	if len(applyArgs) == 1 {
   205  		// If we only have a single output, just generate a normal `.apply` or `.then`.
   206  		g.Fgenf(w, "%.20v.%v(%.v)", applyArgs[0], apply, then)
   207  	} else {
   208  		// Otherwise, generate a call to `pulumi.all([]).apply()`.
   209  		g.Fgenf(w, "%v([", all)
   210  		for i, o := range applyArgs {
   211  			if i > 0 {
   212  				g.Fgen(w, ", ")
   213  			}
   214  			g.Fgenf(w, "%v", o)
   215  		}
   216  		g.Fgenf(w, "]).%v(%.v)", apply, then)
   217  	}
   218  }
   219  
   220  // functionName computes the NodeJS package, module, and name for the given function token.
   221  func functionName(tokenArg model.Expression) (string, string, string, hcl.Diagnostics) {
   222  	token := tokenArg.(*model.TemplateExpression).Parts[0].(*model.LiteralValueExpression).Value.AsString()
   223  	tokenRange := tokenArg.SyntaxNode().Range()
   224  
   225  	// Compute the resource type from the Pulumi type token.
   226  	pkg, module, member, diagnostics := pcl.DecomposeToken(token, tokenRange)
   227  	return pkg, strings.Replace(module, "/", ".", -1), member, diagnostics
   228  }
   229  
   230  func (g *generator) genRange(w io.Writer, call *model.FunctionCallExpression, entries bool) {
   231  	var from, to model.Expression
   232  	switch len(call.Args) {
   233  	case 1:
   234  		from, to = &model.LiteralValueExpression{Value: cty.NumberIntVal(0)}, call.Args[0]
   235  	case 2:
   236  		from, to = call.Args[0], call.Args[1]
   237  	default:
   238  		contract.Failf("expected range() to have exactly 1 or 2 args; got %v", len(call.Args))
   239  	}
   240  
   241  	genPrefix := func() { g.Fprint(w, "((from, to) => (new Array(to - from))") }
   242  	mapValue := "from + i"
   243  	genSuffix := func() { g.Fgenf(w, ")(%.v, %.v)", from, to) }
   244  
   245  	if litFrom, ok := from.(*model.LiteralValueExpression); ok {
   246  		fromV, err := convert.Convert(litFrom.Value, cty.Number)
   247  		contract.Assert(err == nil)
   248  
   249  		from, _ := fromV.AsBigFloat().Int64()
   250  		if litTo, ok := to.(*model.LiteralValueExpression); ok {
   251  			toV, err := convert.Convert(litTo.Value, cty.Number)
   252  			contract.Assert(err == nil)
   253  
   254  			to, _ := toV.AsBigFloat().Int64()
   255  			if from == 0 {
   256  				mapValue = "i"
   257  			} else {
   258  				mapValue = fmt.Sprintf("%d + i", from)
   259  			}
   260  			genPrefix = func() { g.Fprintf(w, "(new Array(%d))", to-from) }
   261  			genSuffix = func() {}
   262  		} else if from == 0 {
   263  			genPrefix = func() { g.Fgenf(w, "(new Array(%.v))", to) }
   264  			mapValue = "i"
   265  			genSuffix = func() {}
   266  		}
   267  	}
   268  
   269  	if entries {
   270  		mapValue = fmt.Sprintf("{key: %[1]s, value: %[1]s}", mapValue)
   271  	}
   272  
   273  	genPrefix()
   274  	g.Fprintf(w, ".map((_, i) => %v)", mapValue)
   275  	genSuffix()
   276  }
   277  
   278  var functionImports = map[string][]string{
   279  	intrinsicInterpolate: {"@pulumi/pulumi"},
   280  	"fileArchive":        {"@pulumi/pulumi"},
   281  	"remoteArchive":      {"@pulumi/pulumi"},
   282  	"assetArchive":       {"@pulumi/pulumi"},
   283  	"fileAsset":          {"@pulumi/pulumi"},
   284  	"stringAsset":        {"@pulumi/pulumi"},
   285  	"remoteAsset":        {"@pulumi/pulumi"},
   286  	"filebase64":         {"fs"},
   287  	"filebase64sha256":   {"fs", "crypto"},
   288  	"readFile":           {"fs"},
   289  	"readDir":            {"fs"},
   290  	"sha1":               {"crypto"},
   291  }
   292  
   293  func (g *generator) getFunctionImports(x *model.FunctionCallExpression) []string {
   294  	if x.Name != pcl.Invoke {
   295  		return functionImports[x.Name]
   296  	}
   297  
   298  	pkg, _, _, diags := functionName(x.Args[0])
   299  	contract.Assert(len(diags) == 0)
   300  	return []string{"@pulumi/" + pkg}
   301  }
   302  
   303  func enumName(enum *model.EnumType) (string, error) {
   304  	components := strings.Split(enum.Token, ":")
   305  	contract.Assertf(len(components) == 3, "malformed token %v", enum.Token)
   306  	name := tokenToName(enum.Token)
   307  	pkg := makeValidIdentifier(components[0])
   308  	e, ok := pcl.GetSchemaForType(enum)
   309  	if !ok {
   310  		return "", fmt.Errorf("Could not get associated enum")
   311  	}
   312  	if name := e.(*schema.EnumType).Package.Language["nodejs"].(NodePackageInfo).PackageName; name != "" {
   313  		pkg = name
   314  	}
   315  	if mod := components[1]; mod != "" && mod != "index" {
   316  		if pkg := e.(*schema.EnumType).Package; pkg != nil {
   317  			mod = moduleName(mod, pkg)
   318  		}
   319  		pkg += "." + mod
   320  	}
   321  	return fmt.Sprintf("%s.%s", pkg, name), nil
   322  }
   323  
   324  func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionCallExpression) {
   325  	switch expr.Name {
   326  	case pcl.IntrinsicConvert:
   327  		from := expr.Args[0]
   328  		to := pcl.LowerConversion(from, expr.Signature.ReturnType)
   329  		output, isOutput := to.(*model.OutputType)
   330  		if isOutput {
   331  			to = output.ElementType
   332  		}
   333  		switch to := to.(type) {
   334  		case *model.EnumType:
   335  			if enum, err := enumName(to); err == nil {
   336  				if isOutput {
   337  					g.Fgenf(w, "%.v.apply((x) => %s[x])", from, enum)
   338  				} else {
   339  					pcl.GenEnum(to, from, func(member *schema.Enum) {
   340  						memberTag, err := enumMemberName(tokenToName(to.Token), member)
   341  						contract.AssertNoErrorf(err, "Failed to get member name on enum '%s'", enum)
   342  						g.Fgenf(w, "%s.%s", enum, memberTag)
   343  					}, func(from model.Expression) {
   344  						g.Fgenf(w, "%s[%.v]", enum, from)
   345  					})
   346  				}
   347  			} else {
   348  				g.Fgenf(w, "%v", from)
   349  			}
   350  		default:
   351  			g.Fgenf(w, "%v", from)
   352  		}
   353  	case pcl.IntrinsicApply:
   354  		g.genApply(w, expr)
   355  	case intrinsicAwait:
   356  		g.Fgenf(w, "await %.17v", expr.Args[0])
   357  	case intrinsicInterpolate:
   358  		g.Fgen(w, "pulumi.interpolate`")
   359  		for _, part := range expr.Args {
   360  			if lit, ok := part.(*model.LiteralValueExpression); ok && model.StringType.AssignableFrom(lit.Type()) {
   361  				g.Fgen(w, lit.Value.AsString())
   362  			} else {
   363  				g.Fgenf(w, "${%.v}", part)
   364  			}
   365  		}
   366  		g.Fgen(w, "`")
   367  	case "element":
   368  		g.Fgenf(w, "%.20v[%.v]", expr.Args[0], expr.Args[1])
   369  	case "entries":
   370  		switch model.ResolveOutputs(expr.Args[0].Type()).(type) {
   371  		case *model.ListType, *model.TupleType:
   372  			if call, ok := expr.Args[0].(*model.FunctionCallExpression); ok && call.Name == "range" {
   373  				g.genRange(w, call, true)
   374  				return
   375  			}
   376  			g.Fgenf(w, "%.20v.map((k, v)", expr.Args[0])
   377  		case *model.MapType, *model.ObjectType:
   378  			g.Fgenf(w, "Object.entries(%.v).map(([k, v])", expr.Args[0])
   379  		}
   380  		g.Fgenf(w, " => {key: k, value: v})")
   381  	case "fileArchive":
   382  		g.Fgenf(w, "new pulumi.asset.FileArchive(%.v)", expr.Args[0])
   383  	case "remoteArchive":
   384  		g.Fgenf(w, "new pulumi.asset.RemoteArchive(%.v)", expr.Args[0])
   385  	case "assetArchive":
   386  		g.Fgenf(w, "new pulumi.asset.AssetArchive(%.v)", expr.Args[0])
   387  	case "fileAsset":
   388  		g.Fgenf(w, "new pulumi.asset.FileAsset(%.v)", expr.Args[0])
   389  	case "stringAsset":
   390  		g.Fgenf(w, "new pulumi.asset.StringAsset(%.v)", expr.Args[0])
   391  	case "remoteAsset":
   392  		g.Fgenf(w, "new pulumi.asset.RemoteAsset(%.v)", expr.Args[0])
   393  	case "filebase64":
   394  		g.Fgenf(w, "Buffer.from(fs.readFileSync(%v), 'binary').toString('base64')", expr.Args[0])
   395  	case "filebase64sha256":
   396  		// Assuming the existence of the following helper method
   397  		g.Fgenf(w, "computeFilebase64sha256(%v)", expr.Args[0])
   398  	case pcl.Invoke:
   399  		pkg, module, fn, diags := functionName(expr.Args[0])
   400  		contract.Assert(len(diags) == 0)
   401  		if module != "" {
   402  			module = "." + module
   403  		}
   404  		isOut := pcl.IsOutputVersionInvokeCall(expr)
   405  		name := fmt.Sprintf("%s%s.%s", makeValidIdentifier(pkg), module, fn)
   406  		if isOut {
   407  			name = fmt.Sprintf("%sOutput", name)
   408  		}
   409  		g.Fprintf(w, "%s(", name)
   410  		if len(expr.Args) >= 2 {
   411  			g.Fgenf(w, "%.v", expr.Args[1])
   412  		}
   413  		if len(expr.Args) == 3 {
   414  			g.Fgenf(w, ", %.v", expr.Args[2])
   415  		}
   416  		g.Fprint(w, ")")
   417  	case "join":
   418  		g.Fgenf(w, "%.20v.join(%v)", expr.Args[1], expr.Args[0])
   419  	case "length":
   420  		g.Fgenf(w, "%.20v.length", expr.Args[0])
   421  	case "lookup":
   422  		g.Fgenf(w, "%v[%v]", expr.Args[0], expr.Args[1])
   423  		if len(expr.Args) == 3 {
   424  			g.Fgenf(w, " || %v", expr.Args[2])
   425  		}
   426  	case "range":
   427  		g.genRange(w, expr, false)
   428  	case "readFile":
   429  		g.Fgenf(w, "fs.readFileSync(%v)", expr.Args[0])
   430  	case "readDir":
   431  		g.Fgenf(w, "fs.readDirSync(%v)", expr.Args[0])
   432  	case "secret":
   433  		g.Fgenf(w, "pulumi.secret(%v)", expr.Args[0])
   434  	case "split":
   435  		g.Fgenf(w, "%.20v.split(%v)", expr.Args[1], expr.Args[0])
   436  	case "toBase64":
   437  		g.Fgenf(w, "Buffer.from(%v).toString(\"base64\")", expr.Args[0])
   438  	case "fromBase64":
   439  		g.Fgenf(w, "Buffer.from(%v, \"base64\").toString(\"utf8\")", expr.Args[0])
   440  	case "toJSON":
   441  		g.Fgenf(w, "JSON.stringify(%v)", expr.Args[0])
   442  	case "sha1":
   443  		g.Fgenf(w, "crypto.createHash('sha1').update(%v).digest('hex')", expr.Args[0])
   444  	case "stack":
   445  		g.Fgenf(w, "pulumi.getStack()")
   446  	case "project":
   447  		g.Fgenf(w, "pulumi.getProject()")
   448  	case "cwd":
   449  		g.Fgen(w, "process.cwd()")
   450  
   451  	default:
   452  		var rng hcl.Range
   453  		if expr.Syntax != nil {
   454  			rng = expr.Syntax.Range()
   455  		}
   456  		g.genNYI(w, "FunctionCallExpression: %v (%v)", expr.Name, rng)
   457  	}
   458  }
   459  
   460  func (g *generator) GenIndexExpression(w io.Writer, expr *model.IndexExpression) {
   461  	g.Fgenf(w, "%.20v[%.v]", expr.Collection, expr.Key)
   462  }
   463  
   464  func (g *generator) genStringLiteral(w io.Writer, v string) {
   465  	builder := strings.Builder{}
   466  	newlines := strings.Count(v, "\n")
   467  	if newlines == 0 || newlines == 1 && (v[0] == '\n' || v[len(v)-1] == '\n') {
   468  		// This string either does not contain newlines or contains a single leading or trailing newline, so we'll
   469  		// Generate a normal string literal. Quotes, backslashes, and newlines will be escaped in conformance with
   470  		// ECMA-262 11.8.4 ("String Literals").
   471  		builder.WriteRune('"')
   472  		for _, c := range v {
   473  			if c == '\n' {
   474  				builder.WriteString(`\n`)
   475  			} else {
   476  				if c == '"' || c == '\\' {
   477  					builder.WriteRune('\\')
   478  				}
   479  				builder.WriteRune(c)
   480  			}
   481  		}
   482  		builder.WriteRune('"')
   483  	} else {
   484  		// This string does contain newlines, so we'll Generate a template string literal. "${", backquotes, and
   485  		// backslashes will be escaped in conformance with ECMA-262 11.8.6 ("Template Literal Lexical Components").
   486  		runes := []rune(v)
   487  		builder.WriteRune('`')
   488  		for i, c := range runes {
   489  			switch c {
   490  			case '$':
   491  				if i < len(runes)-1 && runes[i+1] == '{' {
   492  					builder.WriteRune('\\')
   493  				}
   494  			case '`', '\\':
   495  				builder.WriteRune('\\')
   496  			}
   497  			builder.WriteRune(c)
   498  		}
   499  		builder.WriteRune('`')
   500  	}
   501  
   502  	g.Fgenf(w, "%s", builder.String())
   503  }
   504  
   505  func (g *generator) GenLiteralValueExpression(w io.Writer, expr *model.LiteralValueExpression) {
   506  	typ := expr.Type()
   507  	if cns, ok := typ.(*model.ConstType); ok {
   508  		typ = cns.Type
   509  	}
   510  
   511  	switch typ {
   512  	case model.BoolType:
   513  		g.Fgenf(w, "%v", expr.Value.True())
   514  	case model.NoneType:
   515  		g.Fgen(w, "undefined")
   516  	case model.NumberType:
   517  		bf := expr.Value.AsBigFloat()
   518  		if i, acc := bf.Int64(); acc == big.Exact {
   519  			g.Fgenf(w, "%d", i)
   520  		} else {
   521  			f, _ := bf.Float64()
   522  			g.Fgenf(w, "%g", f)
   523  		}
   524  	case model.StringType:
   525  		g.genStringLiteral(w, expr.Value.AsString())
   526  	default:
   527  		contract.Failf("unexpected literal type in GenLiteralValueExpression: %v (%v)", expr.Type(),
   528  			expr.SyntaxNode().Range())
   529  	}
   530  }
   531  
   532  func (g *generator) literalKey(x model.Expression) (string, bool) {
   533  	strKey := ""
   534  	switch x := x.(type) {
   535  	case *model.LiteralValueExpression:
   536  		if model.StringType.AssignableFrom(x.Type()) {
   537  			strKey = x.Value.AsString()
   538  			break
   539  		}
   540  		var buf bytes.Buffer
   541  		g.GenLiteralValueExpression(&buf, x)
   542  		return buf.String(), true
   543  	case *model.TemplateExpression:
   544  		if len(x.Parts) == 1 {
   545  			if lit, ok := x.Parts[0].(*model.LiteralValueExpression); ok && model.StringType.AssignableFrom(lit.Type()) {
   546  				strKey = lit.Value.AsString()
   547  				break
   548  			}
   549  		}
   550  		var buf bytes.Buffer
   551  		g.GenTemplateExpression(&buf, x)
   552  		return buf.String(), true
   553  	default:
   554  		return "", false
   555  	}
   556  
   557  	if isLegalIdentifier(strKey) {
   558  		return strKey, true
   559  	}
   560  	return fmt.Sprintf("%q", strKey), true
   561  
   562  }
   563  
   564  func (g *generator) GenObjectConsExpression(w io.Writer, expr *model.ObjectConsExpression) {
   565  	if len(expr.Items) == 0 {
   566  		g.Fgen(w, "{}")
   567  	} else {
   568  		g.Fgen(w, "{")
   569  		g.Indented(func() {
   570  			for _, item := range expr.Items {
   571  				g.Fgenf(w, "\n%s", g.Indent)
   572  				if lit, ok := g.literalKey(item.Key); ok {
   573  					g.Fgenf(w, "%s", lit)
   574  				} else {
   575  					g.Fgenf(w, "[%.v]", item.Key)
   576  				}
   577  				g.Fgenf(w, ": %.v,", item.Value)
   578  			}
   579  		})
   580  		g.Fgenf(w, "\n%s}", g.Indent)
   581  	}
   582  }
   583  
   584  func (g *generator) genRelativeTraversal(w io.Writer, traversal hcl.Traversal, parts []model.Traversable) {
   585  	for i, part := range traversal {
   586  		var key cty.Value
   587  		switch part := part.(type) {
   588  		case hcl.TraverseAttr:
   589  			key = cty.StringVal(part.Name)
   590  		case hcl.TraverseIndex:
   591  			key = part.Key
   592  		default:
   593  			contract.Failf("unexpected traversal part of type %T (%v)", part, part.SourceRange())
   594  		}
   595  
   596  		if model.IsOptionalType(model.GetTraversableType(parts[i])) {
   597  			g.Fgen(w, "?")
   598  		}
   599  
   600  		switch key.Type() {
   601  		case cty.String:
   602  			keyVal := key.AsString()
   603  			if isLegalIdentifier(keyVal) {
   604  				g.Fgenf(w, ".%s", keyVal)
   605  			} else {
   606  				g.Fgenf(w, "[%q]", keyVal)
   607  			}
   608  		case cty.Number:
   609  			idx, _ := key.AsBigFloat().Int64()
   610  			g.Fgenf(w, "[%d]", idx)
   611  		default:
   612  			g.Fgenf(w, "[%q]", key.AsString())
   613  		}
   614  	}
   615  }
   616  
   617  func (g *generator) GenRelativeTraversalExpression(w io.Writer, expr *model.RelativeTraversalExpression) {
   618  	g.Fgenf(w, "%.20v", expr.Source)
   619  	g.genRelativeTraversal(w, expr.Traversal, expr.Parts)
   620  }
   621  
   622  func (g *generator) GenScopeTraversalExpression(w io.Writer, expr *model.ScopeTraversalExpression) {
   623  	rootName := makeValidIdentifier(expr.RootName)
   624  	if _, ok := expr.Parts[0].(*model.SplatVariable); ok {
   625  		rootName = "__item"
   626  	}
   627  
   628  	g.Fgen(w, rootName)
   629  	g.genRelativeTraversal(w, expr.Traversal.SimpleSplit().Rel, expr.Parts)
   630  }
   631  
   632  func (g *generator) GenSplatExpression(w io.Writer, expr *model.SplatExpression) {
   633  	g.Fgenf(w, "%.20v.map(__item => %.v)", expr.Source, expr.Each)
   634  }
   635  
   636  func (g *generator) GenTemplateExpression(w io.Writer, expr *model.TemplateExpression) {
   637  	if len(expr.Parts) == 1 {
   638  		if lit, ok := expr.Parts[0].(*model.LiteralValueExpression); ok && model.StringType.AssignableFrom(lit.Type()) {
   639  			g.GenLiteralValueExpression(w, lit)
   640  			return
   641  		}
   642  	}
   643  
   644  	g.Fgen(w, "`")
   645  	for _, expr := range expr.Parts {
   646  		if lit, ok := expr.(*model.LiteralValueExpression); ok && model.StringType.AssignableFrom(lit.Type()) {
   647  			g.Fgen(w, lit.Value.AsString())
   648  		} else {
   649  			g.Fgenf(w, "${%.v}", expr)
   650  		}
   651  	}
   652  	g.Fgen(w, "`")
   653  }
   654  
   655  func (g *generator) GenTemplateJoinExpression(w io.Writer, expr *model.TemplateJoinExpression) {
   656  	g.genNYI(w, "TemplateJoinExpression")
   657  }
   658  
   659  func (g *generator) GenTupleConsExpression(w io.Writer, expr *model.TupleConsExpression) {
   660  	switch len(expr.Expressions) {
   661  	case 0:
   662  		g.Fgen(w, "[]")
   663  	case 1:
   664  		g.Fgenf(w, "[%.v]", expr.Expressions[0])
   665  	default:
   666  		g.Fgen(w, "[")
   667  		g.Indented(func() {
   668  			for _, v := range expr.Expressions {
   669  				g.Fgenf(w, "\n%s%.v,", g.Indent, v)
   670  			}
   671  		})
   672  		g.Fgen(w, "\n", g.Indent, "]")
   673  	}
   674  }
   675  
   676  func (g *generator) GenUnaryOpExpression(w io.Writer, expr *model.UnaryOpExpression) {
   677  	opstr, precedence := "", g.GetPrecedence(expr)
   678  	switch expr.Operation {
   679  	case hclsyntax.OpLogicalNot:
   680  		opstr = "!"
   681  	case hclsyntax.OpNegate:
   682  		opstr = "-"
   683  	}
   684  	g.Fgenf(w, "%[2]v%.[1]*[3]v", precedence, opstr, expr.Operand)
   685  }