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

     1  // Copyright 2016-2020, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package nodejs
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"io/ioutil"
    22  	"path"
    23  	"sort"
    24  	"strings"
    25  
    26  	"github.com/hashicorp/hcl/v2"
    27  	"github.com/pulumi/pulumi/pkg/v3/codegen"
    28  	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
    29  	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model/format"
    30  	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
    31  	"github.com/pulumi/pulumi/pkg/v3/codegen/pcl"
    32  	"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
    33  	"github.com/pulumi/pulumi/sdk/v3/go/common/encoding"
    34  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    35  	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
    36  	"github.com/zclconf/go-cty/cty"
    37  )
    38  
    39  const PulumiToken = "pulumi"
    40  
    41  type generator struct {
    42  	// The formatter to use when generating code.
    43  	*format.Formatter
    44  
    45  	program     *pcl.Program
    46  	diagnostics hcl.Diagnostics
    47  
    48  	asyncMain     bool
    49  	configCreated bool
    50  }
    51  
    52  func GenerateProgram(program *pcl.Program) (map[string][]byte, hcl.Diagnostics, error) {
    53  	pcl.MapProvidersAsResources(program)
    54  	// Linearize the nodes into an order appropriate for procedural code generation.
    55  	nodes := pcl.Linearize(program)
    56  
    57  	g := &generator{
    58  		program: program,
    59  	}
    60  	g.Formatter = format.NewFormatter(g)
    61  
    62  	// Creating a list to store and later print helper methods if they turn out to be needed
    63  	preambleHelperMethods := codegen.NewStringSet()
    64  
    65  	packages, err := program.PackageSnapshots()
    66  	if err != nil {
    67  		return nil, nil, err
    68  	}
    69  	for _, p := range packages {
    70  		if err := p.ImportLanguages(map[string]schema.Language{"nodejs": Importer}); err != nil {
    71  			return nil, nil, err
    72  		}
    73  	}
    74  
    75  	var index bytes.Buffer
    76  	g.genPreamble(&index, program, preambleHelperMethods)
    77  	for _, n := range nodes {
    78  		if g.asyncMain {
    79  			break
    80  		}
    81  		switch x := n.(type) {
    82  		case *pcl.Resource:
    83  			if resourceRequiresAsyncMain(x) {
    84  				g.asyncMain = true
    85  			}
    86  		case *pcl.OutputVariable:
    87  			if outputRequiresAsyncMain(x) {
    88  				g.asyncMain = true
    89  			}
    90  		}
    91  	}
    92  
    93  	indenter := func(f func()) { f() }
    94  	if g.asyncMain {
    95  		indenter = g.Indented
    96  		g.Fgenf(&index, "export = async () => {\n")
    97  	}
    98  
    99  	indenter(func() {
   100  		for _, n := range nodes {
   101  			g.genNode(&index, n)
   102  		}
   103  
   104  		if g.asyncMain {
   105  			var result *model.ObjectConsExpression
   106  			for _, n := range nodes {
   107  				if o, ok := n.(*pcl.OutputVariable); ok {
   108  					if result == nil {
   109  						result = &model.ObjectConsExpression{}
   110  					}
   111  					name := o.LogicalName()
   112  					nameVar := makeValidIdentifier(o.Name())
   113  					result.Items = append(result.Items, model.ObjectConsItem{
   114  						Key: &model.LiteralValueExpression{Value: cty.StringVal(name)},
   115  						Value: &model.ScopeTraversalExpression{
   116  							RootName:  nameVar,
   117  							Traversal: hcl.Traversal{hcl.TraverseRoot{Name: name}},
   118  							Parts: []model.Traversable{&model.Variable{
   119  								Name:         nameVar,
   120  								VariableType: o.Type(),
   121  							}},
   122  						},
   123  					})
   124  				}
   125  			}
   126  			if result != nil {
   127  				g.Fgenf(&index, "%sreturn %v;\n", g.Indent, result)
   128  			}
   129  		}
   130  
   131  	})
   132  
   133  	if g.asyncMain {
   134  		g.Fgenf(&index, "}\n")
   135  	}
   136  
   137  	files := map[string][]byte{
   138  		"index.ts": index.Bytes(),
   139  	}
   140  	return files, g.diagnostics, nil
   141  }
   142  
   143  func GenerateProject(directory string, project workspace.Project, program *pcl.Program) error {
   144  	files, diagnostics, err := GenerateProgram(program)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	if diagnostics.HasErrors() {
   149  		return diagnostics
   150  	}
   151  
   152  	// Set the runtime to "nodejs" then marshal to Pulumi.yaml
   153  	project.Runtime = workspace.NewProjectRuntimeInfo("nodejs", nil)
   154  	projectBytes, err := encoding.YAML.Marshal(project)
   155  	if err != nil {
   156  		return err
   157  	}
   158  	files["Pulumi.yaml"] = projectBytes
   159  
   160  	// Build the pacakge.json
   161  	var packageJSON bytes.Buffer
   162  	packageJSON.WriteString(fmt.Sprintf(`{
   163  		"name": "%s",
   164  		"devDependencies": {
   165  			"@types/node": "^14"
   166  		},
   167  		"dependencies": {
   168  			"typescript": "^4.0.0",
   169  			"@pulumi/pulumi": "^3.0.0"`, project.Name.String()))
   170  	// For each package add a dependency line
   171  	packages, err := program.PackageSnapshots()
   172  	if err != nil {
   173  		return err
   174  	}
   175  	for _, p := range packages {
   176  		if p.Name == PulumiToken {
   177  			continue
   178  		}
   179  		if err := p.ImportLanguages(map[string]schema.Language{"nodejs": Importer}); err != nil {
   180  			return err
   181  		}
   182  
   183  		packageName := "@pulumi/" + p.Name
   184  		err := p.ImportLanguages(map[string]schema.Language{"nodejs": Importer})
   185  		if err != nil {
   186  			return err
   187  		}
   188  		if langInfo, found := p.Language["nodejs"]; found {
   189  			nodeInfo, ok := langInfo.(NodePackageInfo)
   190  			if ok && nodeInfo.PackageName != "" {
   191  				packageName = nodeInfo.PackageName
   192  			}
   193  		}
   194  		dependencyTemplate := ",\n			\"%s\": \"%s\""
   195  		if p.Version != nil {
   196  			packageJSON.WriteString(fmt.Sprintf(dependencyTemplate, packageName, p.Version.String()))
   197  		} else {
   198  			packageJSON.WriteString(fmt.Sprintf(dependencyTemplate, packageName, "*"))
   199  		}
   200  	}
   201  	packageJSON.WriteString(`
   202  		}
   203  }`)
   204  
   205  	files["package.json"] = packageJSON.Bytes()
   206  
   207  	// Add the language specific .gitignore
   208  	files[".gitignore"] = []byte(`/bin/
   209  /node_modules/`)
   210  
   211  	// Add the basic tsconfig
   212  	var tsConfig bytes.Buffer
   213  	tsConfig.WriteString(`{
   214  		"compilerOptions": {
   215  			"strict": true,
   216  			"outDir": "bin",
   217  			"target": "es2016",
   218  			"module": "commonjs",
   219  			"moduleResolution": "node",
   220  			"sourceMap": true,
   221  			"experimentalDecorators": true,
   222  			"pretty": true,
   223  			"noFallthroughCasesInSwitch": true,
   224  			"noImplicitReturns": true,
   225  			"forceConsistentCasingInFileNames": true
   226  		},
   227  		"files": [
   228  `)
   229  
   230  	for file := range files {
   231  		if strings.HasSuffix(file, ".ts") {
   232  			tsConfig.WriteString("			\"" + file + "\"\n")
   233  		}
   234  	}
   235  
   236  	tsConfig.WriteString(`		]
   237  }`)
   238  	files["tsconfig.json"] = tsConfig.Bytes()
   239  
   240  	for filename, data := range files {
   241  		outPath := path.Join(directory, filename)
   242  		err := ioutil.WriteFile(outPath, data, 0600)
   243  		if err != nil {
   244  			return fmt.Errorf("could not write output program: %w", err)
   245  		}
   246  	}
   247  
   248  	return nil
   249  }
   250  
   251  // genLeadingTrivia generates the list of leading trivia assicated with a given token.
   252  func (g *generator) genLeadingTrivia(w io.Writer, token syntax.Token) {
   253  	// TODO(pdg): whitespace?
   254  	for _, t := range token.LeadingTrivia {
   255  		if c, ok := t.(syntax.Comment); ok {
   256  			g.genComment(w, c)
   257  		}
   258  	}
   259  }
   260  
   261  // genTrailingTrivia generates the list of trailing trivia assicated with a given token.
   262  func (g *generator) genTrailingTrivia(w io.Writer, token syntax.Token) {
   263  	// TODO(pdg): whitespace
   264  	for _, t := range token.TrailingTrivia {
   265  		if c, ok := t.(syntax.Comment); ok {
   266  			g.genComment(w, c)
   267  		}
   268  	}
   269  }
   270  
   271  // genTrivia generates the list of trivia assicated with a given token.
   272  func (g *generator) genTrivia(w io.Writer, token syntax.Token) {
   273  	g.genLeadingTrivia(w, token)
   274  	g.genTrailingTrivia(w, token)
   275  }
   276  
   277  // genComment generates a comment into the output.
   278  func (g *generator) genComment(w io.Writer, comment syntax.Comment) {
   279  	for _, l := range comment.Lines {
   280  		g.Fgenf(w, "%s//%s\n", g.Indent, l)
   281  	}
   282  }
   283  
   284  func (g *generator) genPreamble(w io.Writer, program *pcl.Program, preambleHelperMethods codegen.StringSet) {
   285  	// Print the @pulumi/pulumi import at the top.
   286  	g.Fprintln(w, `import * as pulumi from "@pulumi/pulumi";`)
   287  
   288  	// Accumulate other imports for the various providers and packages. Don't emit them yet, as we need to sort them
   289  	// later on.
   290  	importSet := codegen.NewStringSet("@pulumi/pulumi")
   291  	npmToPuPkgName := make(map[string]string)
   292  	for _, n := range program.Nodes {
   293  		if r, isResource := n.(*pcl.Resource); isResource {
   294  			pkg, _, _, _ := r.DecomposeToken()
   295  			if pkg == PulumiToken {
   296  				continue
   297  			}
   298  			pkgName := "@pulumi/" + pkg
   299  			if r.Schema != nil && r.Schema.Package != nil {
   300  				if info, ok := r.Schema.Package.Language["nodejs"].(NodePackageInfo); ok && info.PackageName != "" {
   301  					pkgName = info.PackageName
   302  				}
   303  				npmToPuPkgName[pkgName] = pkg
   304  			}
   305  			importSet.Add(pkgName)
   306  		}
   307  		diags := n.VisitExpressions(nil, func(n model.Expression) (model.Expression, hcl.Diagnostics) {
   308  			if call, ok := n.(*model.FunctionCallExpression); ok {
   309  				if i := g.getFunctionImports(call); len(i) > 0 && i[0] != "" {
   310  					for _, importPackage := range i {
   311  						importSet.Add(importPackage)
   312  					}
   313  				}
   314  				if helperMethodBody, ok := getHelperMethodIfNeeded(call.Name); ok {
   315  					preambleHelperMethods.Add(helperMethodBody)
   316  				}
   317  			}
   318  			return n, nil
   319  		})
   320  		contract.Assert(len(diags) == 0)
   321  	}
   322  
   323  	var imports []string
   324  	for _, pkg := range importSet.SortedValues() {
   325  		if pkg == "@pulumi/pulumi" {
   326  			continue
   327  		}
   328  		var as string
   329  		if puPkg, ok := npmToPuPkgName[pkg]; ok {
   330  			as = makeValidIdentifier(puPkg)
   331  		} else {
   332  			as = makeValidIdentifier(path.Base(pkg))
   333  		}
   334  		imports = append(imports, fmt.Sprintf("import * as %v from \"%v\";", as, pkg))
   335  	}
   336  	sort.Strings(imports)
   337  
   338  	// Now sort the imports and emit them.
   339  	for _, i := range imports {
   340  		g.Fprintln(w, i)
   341  	}
   342  	g.Fprint(w, "\n")
   343  
   344  	// If we collected any helper methods that should be added, write them just before the main func
   345  	for _, preambleHelperMethodBody := range preambleHelperMethods.SortedValues() {
   346  		g.Fprintf(w, "%s\n\n", preambleHelperMethodBody)
   347  	}
   348  }
   349  
   350  func (g *generator) genNode(w io.Writer, n pcl.Node) {
   351  	switch n := n.(type) {
   352  	case *pcl.Resource:
   353  		g.genResource(w, n)
   354  	case *pcl.ConfigVariable:
   355  		g.genConfigVariable(w, n)
   356  	case *pcl.LocalVariable:
   357  		g.genLocalVariable(w, n)
   358  	case *pcl.OutputVariable:
   359  		g.genOutputVariable(w, n)
   360  	}
   361  }
   362  
   363  func resourceRequiresAsyncMain(r *pcl.Resource) bool {
   364  	if r.Options == nil || r.Options.Range == nil {
   365  		return false
   366  	}
   367  
   368  	return model.ContainsPromises(r.Options.Range.Type())
   369  }
   370  
   371  func outputRequiresAsyncMain(ov *pcl.OutputVariable) bool {
   372  	outputName := ov.LogicalName()
   373  	if makeValidIdentifier(outputName) != outputName {
   374  		return true
   375  	}
   376  
   377  	return false
   378  }
   379  
   380  // resourceTypeName computes the NodeJS package, module, and type name for the given resource.
   381  func resourceTypeName(r *pcl.Resource) (string, string, string, hcl.Diagnostics) {
   382  	// Compute the resource type from the Pulumi type token.
   383  	pcl.FixupPulumiPackageTokens(r)
   384  	pkg, module, member, diagnostics := r.DecomposeToken()
   385  
   386  	if r.Schema != nil {
   387  		module = moduleName(module, r.Schema.Package)
   388  	}
   389  
   390  	return makeValidIdentifier(pkg), module, title(member), diagnostics
   391  }
   392  
   393  func moduleName(module string, pkg *schema.Package) string {
   394  	// Normalize module.
   395  	if pkg != nil {
   396  		err := pkg.ImportLanguages(map[string]schema.Language{"nodejs": Importer})
   397  		contract.AssertNoError(err)
   398  		if lang, ok := pkg.Language["nodejs"]; ok {
   399  			pkgInfo := lang.(NodePackageInfo)
   400  			if m, ok := pkgInfo.ModuleToPackage[module]; ok {
   401  				module = m
   402  			}
   403  		}
   404  	}
   405  	return strings.ToLower(strings.ReplaceAll(module, "/", "."))
   406  }
   407  
   408  // makeResourceName returns the expression that should be emitted for a resource's "name" parameter given its base name
   409  // and the count variable name, if any.
   410  func (g *generator) makeResourceName(baseName, count string) string {
   411  	if count == "" {
   412  		return fmt.Sprintf(`"%s"`, baseName)
   413  	}
   414  	return fmt.Sprintf("`%s-${%s}`", baseName, count)
   415  }
   416  
   417  func (g *generator) genResourceOptions(opts *pcl.ResourceOptions) string {
   418  	if opts == nil {
   419  		return ""
   420  	}
   421  
   422  	// Turn the resource options into an ObjectConsExpression and generate it.
   423  	var object *model.ObjectConsExpression
   424  	appendOption := func(name string, value model.Expression) {
   425  		if object == nil {
   426  			object = &model.ObjectConsExpression{}
   427  		}
   428  		object.Items = append(object.Items, model.ObjectConsItem{
   429  			Key: &model.LiteralValueExpression{
   430  				Tokens: syntax.NewLiteralValueTokens(cty.StringVal(name)),
   431  				Value:  cty.StringVal(name),
   432  			},
   433  			Value: value,
   434  		})
   435  	}
   436  
   437  	if opts.Parent != nil {
   438  		appendOption("parent", opts.Parent)
   439  	}
   440  	if opts.Provider != nil {
   441  		appendOption("provider", opts.Provider)
   442  	}
   443  	if opts.DependsOn != nil {
   444  		appendOption("dependsOn", opts.DependsOn)
   445  	}
   446  	if opts.Protect != nil {
   447  		appendOption("protect", opts.Protect)
   448  	}
   449  	if opts.IgnoreChanges != nil {
   450  		appendOption("ignoreChanges", opts.IgnoreChanges)
   451  	}
   452  
   453  	if object == nil {
   454  		return ""
   455  	}
   456  
   457  	var buffer bytes.Buffer
   458  	g.Fgenf(&buffer, ", %v", g.lowerExpression(object, nil))
   459  	return buffer.String()
   460  }
   461  
   462  // genResource handles the generation of instantiations of non-builtin resources.
   463  func (g *generator) genResource(w io.Writer, r *pcl.Resource) {
   464  	pkg, module, memberName, diagnostics := resourceTypeName(r)
   465  	g.diagnostics = append(g.diagnostics, diagnostics...)
   466  
   467  	if module != "" {
   468  		module = "." + module
   469  	}
   470  
   471  	qualifiedMemberName := fmt.Sprintf("%s%s.%s", pkg, module, memberName)
   472  
   473  	optionsBag := g.genResourceOptions(r.Options)
   474  
   475  	name := r.LogicalName()
   476  	variableName := makeValidIdentifier(r.Name())
   477  
   478  	g.genTrivia(w, r.Definition.Tokens.GetType(""))
   479  	for _, l := range r.Definition.Tokens.GetLabels(nil) {
   480  		g.genTrivia(w, l)
   481  	}
   482  	g.genTrivia(w, r.Definition.Tokens.GetOpenBrace())
   483  
   484  	instantiate := func(resName string) {
   485  		g.Fgenf(w, "new %s(%s, {", qualifiedMemberName, resName)
   486  		indenter := func(f func()) { f() }
   487  		if len(r.Inputs) > 1 {
   488  			indenter = g.Indented
   489  		}
   490  		indenter(func() {
   491  			fmtString := "%s: %.v"
   492  			if len(r.Inputs) > 1 {
   493  				fmtString = "\n" + g.Indent + "%s: %.v,"
   494  			}
   495  
   496  			for _, attr := range r.Inputs {
   497  				propertyName := attr.Name
   498  				if !isLegalIdentifier(propertyName) {
   499  					propertyName = fmt.Sprintf("%q", propertyName)
   500  				}
   501  
   502  				destType, diagnostics := r.InputType.Traverse(hcl.TraverseAttr{Name: attr.Name})
   503  				g.diagnostics = append(g.diagnostics, diagnostics...)
   504  				g.Fgenf(w, fmtString, propertyName,
   505  					g.lowerExpression(attr.Value, destType.(model.Type)))
   506  			}
   507  		})
   508  		if len(r.Inputs) > 1 {
   509  			g.Fgenf(w, "\n%s", g.Indent)
   510  		}
   511  		g.Fgenf(w, "}%s)", optionsBag)
   512  	}
   513  
   514  	if r.Options != nil && r.Options.Range != nil {
   515  		rangeType := model.ResolveOutputs(r.Options.Range.Type())
   516  		rangeExpr := g.lowerExpression(r.Options.Range, rangeType)
   517  
   518  		if model.InputType(model.BoolType).ConversionFrom(rangeType) == model.SafeConversion {
   519  			g.Fgenf(w, "%slet %s: %s | undefined;\n", g.Indent, variableName, qualifiedMemberName)
   520  			g.Fgenf(w, "%sif (%.v) {\n", g.Indent, rangeExpr)
   521  			g.Indented(func() {
   522  				g.Fgenf(w, "%s%s = ", g.Indent, variableName)
   523  				instantiate(g.makeResourceName(name, ""))
   524  				g.Fgenf(w, ";\n")
   525  			})
   526  			g.Fgenf(w, "%s}\n", g.Indent)
   527  		} else {
   528  			g.Fgenf(w, "%sconst %s: %s[] = [];\n", g.Indent, variableName, qualifiedMemberName)
   529  
   530  			resKey := "key"
   531  			if model.InputType(model.NumberType).ConversionFrom(rangeExpr.Type()) != model.NoConversion {
   532  				g.Fgenf(w, "%sfor (const range = {value: 0}; range.value < %.12o; range.value++) {\n", g.Indent, rangeExpr)
   533  				resKey = "value"
   534  			} else {
   535  				rangeExpr := &model.FunctionCallExpression{
   536  					Name: "entries",
   537  					Args: []model.Expression{rangeExpr},
   538  				}
   539  				g.Fgenf(w, "%sfor (const range of %.v) {\n", g.Indent, rangeExpr)
   540  			}
   541  
   542  			resName := g.makeResourceName(name, "range."+resKey)
   543  			g.Indented(func() {
   544  				g.Fgenf(w, "%s%s.push(", g.Indent, variableName)
   545  				instantiate(resName)
   546  				g.Fgenf(w, ");\n")
   547  			})
   548  			g.Fgenf(w, "%s}\n", g.Indent)
   549  		}
   550  	} else {
   551  		g.Fgenf(w, "%sconst %s = ", g.Indent, variableName)
   552  		instantiate(g.makeResourceName(name, ""))
   553  		g.Fgenf(w, ";\n")
   554  	}
   555  
   556  	g.genTrivia(w, r.Definition.Tokens.GetCloseBrace())
   557  }
   558  
   559  func (g *generator) genConfigVariable(w io.Writer, v *pcl.ConfigVariable) {
   560  	// TODO(pdg): trivia
   561  
   562  	if !g.configCreated {
   563  		g.Fprintf(w, "%sconst config = new pulumi.Config();\n", g.Indent)
   564  		g.configCreated = true
   565  	}
   566  
   567  	getType := "Object"
   568  	switch v.Type() {
   569  	case model.StringType:
   570  		getType = ""
   571  	case model.NumberType, model.IntType:
   572  		getType = "Number"
   573  	case model.BoolType:
   574  		getType = "Boolean"
   575  	}
   576  
   577  	getOrRequire := "get"
   578  	if v.DefaultValue == nil {
   579  		getOrRequire = "require"
   580  	}
   581  
   582  	name := makeValidIdentifier(v.Name())
   583  	g.Fgenf(w, "%[1]sconst %[2]s = config.%[3]s%[4]s(\"%[5]s\")",
   584  		g.Indent, name, getOrRequire, getType, v.LogicalName())
   585  	if v.DefaultValue != nil {
   586  		g.Fgenf(w, " || %.v", g.lowerExpression(v.DefaultValue, v.DefaultValue.Type()))
   587  	}
   588  	g.Fgenf(w, ";\n")
   589  }
   590  
   591  func (g *generator) genLocalVariable(w io.Writer, v *pcl.LocalVariable) {
   592  	// TODO(pdg): trivia
   593  	g.Fgenf(w, "%sconst %s = %.3v;\n", g.Indent, v.Name(), g.lowerExpression(v.Definition.Value, v.Type()))
   594  }
   595  
   596  func (g *generator) genOutputVariable(w io.Writer, v *pcl.OutputVariable) {
   597  	// TODO(pdg): trivia
   598  	export := "export "
   599  	if g.asyncMain {
   600  		export = ""
   601  	}
   602  	g.Fgenf(w, "%s%sconst %s = %.3v;\n", g.Indent, export,
   603  		makeValidIdentifier(v.Name()), g.lowerExpression(v.Value, v.Type()))
   604  }
   605  
   606  func (g *generator) genNYI(w io.Writer, reason string, vs ...interface{}) {
   607  	message := fmt.Sprintf("not yet implemented: %s", fmt.Sprintf(reason, vs...))
   608  	g.diagnostics = append(g.diagnostics, &hcl.Diagnostic{
   609  		Severity: hcl.DiagError,
   610  		Summary:  message,
   611  		Detail:   message,
   612  	})
   613  	g.Fgenf(w, "(() => throw new Error(%q))()", fmt.Sprintf(reason, vs...))
   614  }