From cb54f7c08a175950927b785bb8b770ba7864e645 Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Wed, 14 Aug 2024 12:40:25 +0200 Subject: [PATCH] Use ASTs to re-parent and replace examples --- pkg/tfgen/docs.go | 327 ++++++++---------- pkg/tfgen/docs_test.go | 91 ++--- .../TestConvertViaPulumiCLI/schema.json | 2 +- .../test_data/custom-replaces/expected.json | 2 +- pkg/tfgen/test_data/link/expected.json | 2 +- .../signalfx-log-timeline/expected.json | 2 +- pkg/tfgen/test_data/simple/expected.json | 2 +- 7 files changed, 184 insertions(+), 244 deletions(-) diff --git a/pkg/tfgen/docs.go b/pkg/tfgen/docs.go index c6dc8572fe..fa1e4e914d 100644 --- a/pkg/tfgen/docs.go +++ b/pkg/tfgen/docs.go @@ -37,14 +37,15 @@ import ( bf "github.com/russross/blackfriday/v2" "github.com/spf13/afero" "github.com/yuin/goldmark" - gmast "github.com/yuin/goldmark/ast" - gmtext "github.com/yuin/goldmark/text" - "golang.org/x/text/cases" - "golang.org/x/text/language" + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" + "github.com/yuin/goldmark/util" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tf2pulumi/convert" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfgen/parse" + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfgen/parse/section" ) const ( @@ -372,34 +373,17 @@ var ( listMarkerRegex = regexp.MustCompile("[-*+]") ) -func trimFrontMatter(text []byte) []byte { - body, ok := bytes.CutPrefix(text, []byte("---")) - if !ok { - return text - } - idx := bytes.Index(body, []byte("\n---")) - - // Unable to find closing, so just return. - if idx == -1 { - return text - } - return body[idx+3:] -} - -func splitByMarkdownHeaders(text string, level int) [][]string { +func splitByMarkdownHeaders(content string, level int) [][]string { // splitByMarkdownHeaders parses text, then walks the resulting AST to find // appropriate header nodes. It uses the location of these header nodes to split // text into sections, which are then split into lines. - bytes := trimFrontMatter([]byte(text)) - - offset := len(text) - len(bytes) - contract.Assertf(offset >= 0, "The offset generated by chopping of the front-matter cannot be negative") + bytes := []byte(content) gm := goldmark.New(goldmark.WithExtensions(parse.TFRegistryExtension)) headers := []int{} - parse.WalkNode(gm.Parser().Parse(gmtext.NewReader(bytes)), func(heading *gmast.Heading) { + parse.WalkNode(gm.Parser().Parse(text.NewReader(bytes)), func(heading *ast.Heading) { if heading.Level != level { return } @@ -410,9 +394,7 @@ func splitByMarkdownHeaders(text string, level int) [][]string { if bytes[i] == '\n' { headers = append(headers, // +1 to move past the \n - // - // +offset to move past the front-matter (if present) - i+1+offset) + i+1) return } } @@ -424,7 +406,7 @@ func splitByMarkdownHeaders(text string, level int) [][]string { // We now use that information to extract sections from `text`. sections := make([][]string, 0, len(headers)+1) - for _, section := range splitStringsAtIndexes(text, headers) { + for _, section := range splitStringsAtIndexes(content, headers) { sections = append(sections, strings.Split(section, "\n")) } @@ -502,19 +484,110 @@ const ( sectionImports = 5 ) -func (p *tfMarkdownParser) parseSupplementaryExamples() (string, error) { +func (p *tfMarkdownParser) parseSupplementaryExamples() ([]byte, error) { examplesFileName := fmt.Sprintf("docs/%s/%s.examples.md", p.kind, p.rawname) absPath, err := filepath.Abs(examplesFileName) if err != nil { - return "", err + return nil, err } fileBytes, err := os.ReadFile(absPath) if err != nil { p.sink.error("explicitly marked resource documentation for replacement, but found no file at %q", examplesFileName) - return "", err + return nil, err + } + + return fileBytes, nil +} + +type exampleTransformer struct { + replace *struct{ content []byte } +} + +func (e exampleTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { + if e.replace == nil { + e.reformatExamples(node, reader, pc) + } else { + e.replaceExamples(node, reader, pc) + } +} + +var exampleHeaderRegexp = regexp.MustCompile(`(?i)^(Example Usage\s*)(?:(?:(?:for|of|[\pP]+)\s*)?(.*?)\s*)?$`) + +func (e exampleTransformer) reformatExamples(node *ast.Document, reader text.Reader, pc parser.Context) { + // First, find the canonical "Example Usage" section and all other docs sections. + var cannonicalExampleSection *section.Section + var nestedExampleSections []*section.Section + err := ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) { + if entering { + return ast.WalkContinue, nil + } + node, ok := n.(*section.Section) + if !ok || node.FirstChild().(*ast.Heading).Level != 2 { + return ast.WalkContinue, nil + } + + headerText := node.FirstChild().Text(reader.Source()) + matches := exampleHeaderRegexp.FindSubmatch(headerText) + if len(matches) == 0 { + return ast.WalkContinue, nil + } + + if len(matches[1]) == len(headerText) && cannonicalExampleSection == nil { + // We have found a canonical section, so set that and keep going + cannonicalExampleSection = node + return ast.WalkContinue, nil + } + + if len(matches[2]) != 0 { + header := node.FirstChild() + header.RemoveChildren(header) + header.AppendChild(header, ast.NewString(matches[2])) + } + + nestedExampleSections = append(nestedExampleSections, node) + return ast.WalkContinue, nil + }) + contract.AssertNoErrorf(err, "impossible - the walker doesn't return an error") + + // TODO: transform H4 sections that contain code snippets into H3 sections. + // + // This function used to be called fixExampleTitles. + + // If there are no nested sections, we don't want to create an empty "## Example + // Usage" section. + if len(nestedExampleSections) == 0 { + return } - return string(fileBytes), nil + // If no canonical section exists, create it at the end of the document. + if cannonicalExampleSection == nil { + h2 := ast.NewHeading(2) + h2.AppendChild(h2, ast.NewString([]byte("Example Usage"))) + section := section.New(h2) + node.AppendChild(node, section) + cannonicalExampleSection = section + } + + // Append all non-canonical sections to the canonical section, setting the level + // to 3. + for _, section := range nestedExampleSections { + header := section.FirstChild().(*ast.Heading) + header.Level = 3 + + cannonicalExampleSection.AppendChild(cannonicalExampleSection, section) + } +} + +// replaceExamples removes any sections that have a header that contains "Example Usage", +// then appends e.replace.content to the end of the document. +func (e exampleTransformer) replaceExamples(node *ast.Document, reader text.Reader, pc parser.Context) { + // We remove existing examples + skipExamples := func(headerText string) bool { + return strings.Contains(headerText, "Example Usage") + } + sectionSkipper{skipExamples}.Transform(node, reader, pc) + + node.AppendChild(node, ast.NewString(e.replace.content)) } func (p *tfMarkdownParser) parse(tfMarkdown []byte) (entityDocs, error) { @@ -527,40 +600,39 @@ func (p *tfMarkdownParser) parse(tfMarkdown []byte) (entityDocs, error) { if err != nil { return entityDocs{}, fmt.Errorf("file %s: %w", p.markdownFileName, err) } - markdown := string(tfMarkdown) // Replace any Windows-style newlines. - markdown = strings.Replace(markdown, "\r\n", "\n", -1) + tfMarkdown = bytes.ReplaceAll(tfMarkdown, []byte("\r\n"), []byte("\n")) // Replace redundant comment. - markdown = strings.Replace(markdown, "", "", -1) - - // Split the sections by H2 topics in the Markdown file. - sections := splitByMarkdownHeaders(markdown, 2) + tfMarkdown = bytes.ReplaceAll(tfMarkdown, []byte(""), nil) - // we are explicitly overwriting the Terraform examples here + var exampleTransformer exampleTransformer if p.info != nil && p.info.GetDocs() != nil && p.info.ReplaceExamplesSection() { - for i, section := range sections { - // Let's remove any existing examples usage we have in our parsed documentation - if len(section) > 0 && strings.Contains(section[0], "Example Usage") { - sections = append(sections[:i], sections[i+1:]...) - - break - } - } - - // now we are going to inject the new source of examples - newExamples, err := p.parseSupplementaryExamples() + b, err := p.parseSupplementaryExamples() if err != nil { return entityDocs{}, err } - newSection := strings.Split(newExamples, "\n") - sections = append(sections, newSection) - } else { - // Reparent examples that are peers of the "Example Usage" section (if any) and fixup some example titles. - sections = reformatExamples(sections) + exampleTransformer.replace = &struct{ content []byte }{b} } + var out bytes.Buffer + err = goldmark.New( + goldmark.WithExtensions(parse.TFRegistryExtension), + goldmark.WithParserOptions(parser.WithASTTransformers( + util.Prioritized(exampleTransformer, 2000), + )), + goldmark.WithRenderer(parse.RenderMarkdown()), + ).Convert(tfMarkdown, &out) + if err != nil { + return entityDocs{}, err + } + + markdown := out.String() + + // Split the sections by H2 topics in the Markdown file. + sections := splitByMarkdownHeaders(markdown, 2) + for _, section := range sections { if err := p.parseSection(section); err != nil { return entityDocs{}, err @@ -574,115 +646,6 @@ func (p *tfMarkdownParser) parse(tfMarkdown []byte) (entityDocs, error) { return doc, nil } -// fixExampleTitles transforms H4 sections that contain code snippets into H3 sections. -func fixExampleTitles(lines []string) { - inSection, sectionIndex := false, 0 - for i, line := range lines { - if inSection && strings.HasPrefix(line, "```") { - lines[sectionIndex] = strings.Replace(lines[sectionIndex], "#### ", "### ", 1) - inSection = false - } else if strings.HasPrefix(line, "#### ") { - inSection, sectionIndex = true, i - } - } -} - -var exampleHeaderRegexp = regexp.MustCompile(`(?i)^(## Example Usage\s*)(?:(?:(?:for|of|[\pP]+)\s*)?(.*?)\s*)?$`) - -// reformatExamples reparents examples that are peers of the "Example Usage" section (if any) and fixup some example -// titles. -func reformatExamples(sections [][]string) [][]string { - canonicalExampleUsageSectionIndex := -1 - var exampleUsageSection []string - var exampleSectionIndices []int - for i, s := range sections { - if len(s) == 0 { - // Skip empty sections. - continue - } - matches := exampleHeaderRegexp.FindStringSubmatch(s[0]) - if len(matches) == 0 { - continue - } - - if len(matches[1]) == len(s[0]) { - // This is the canonical example usage section. Prepend its contents to any other content we've collected. - // If there are multiple canonical example usage sections, treat the first such section as the canonical - // example usage section and append other sections under an H3. - if canonicalExampleUsageSectionIndex == -1 { - canonicalExampleUsageSectionIndex = i - - // Copy the section over. Note that we intentionally avoid copying the first line and any whitespace - // that follows it, as we will overwrite that content with the canonical header later. - for s = s[1:]; len(s) > 0 && isBlank(s[0]); { - s = s[1:] - } - - sectionCopy := make([]string, len(s)+2) - copy(sectionCopy[2:], s) - - if len(exampleUsageSection) != 0 { - exampleUsageSection = append(sectionCopy, exampleUsageSection...) - } else { - exampleUsageSection = sectionCopy - } - } else { - exampleUsageSection = append(exampleUsageSection, "", "### Additional Examples") - exampleUsageSection = append(exampleUsageSection, s[1:]...) - } - } else if strings.Contains(s[0], "## Example Usage -") { - // this is a specific usecase where all of the examples are being requalified as top level examples with a - // title. We should process these as children of the top level examples - exampleUsageSection = append(exampleUsageSection, "### "+cases.Title(language.Und, cases.NoLower).String(matches[2])) - exampleUsageSection = append(exampleUsageSection, s[1:]...) - } else { - // This is a qualified example usage section. Retitle it using an H3 and its qualifier, and append it to - // the output. - exampleUsageSection = append(exampleUsageSection, "", "### "+ - cases.Title(language.Und, cases.NoLower).String(matches[2])) - exampleUsageSection = append(exampleUsageSection, s[1:]...) - } - - exampleSectionIndices = append(exampleSectionIndices, i) - } - - if len(exampleSectionIndices) == 0 { - return sections - } - - // If we did not find a canonical example usage section, prepend a blank line to the output. This line will be - // replaced by the canonical example usage H2. - if canonicalExampleUsageSectionIndex == -1 { - canonicalExampleUsageSectionIndex = exampleSectionIndices[0] - exampleUsageSection = append([]string{""}, exampleUsageSection...) - } - - // Ensure that the output begins with the canonical example usage header. - exampleUsageSection[0] = "## Example Usage" - - // Fixup example titles and replace the contents of the canonical example usage section with the output. - fixExampleTitles(exampleUsageSection) - sections[canonicalExampleUsageSectionIndex] = exampleUsageSection - - // If there is only one example section, we're done. Otherwise, we need to remove all non-canonical example usage - // sections. - if len(exampleSectionIndices) == 1 { - return sections - } - - result := sections[:0] - for i, s := range sections { - if len(exampleSectionIndices) > 0 && i == exampleSectionIndices[0] { - exampleSectionIndices = exampleSectionIndices[1:] - if i != canonicalExampleUsageSectionIndex { - continue - } - } - result = append(result, s) - } - return result -} - func (p *tfMarkdownParser) parseSection(h2Section []string) error { // Extract the header name, since this will drive how we process the content. if len(h2Section) == 0 { @@ -690,29 +653,45 @@ func (p *tfMarkdownParser) parseSection(h2Section []string) error { return nil } + sectionKind := sectionOther + // Skip certain headers that we don't support. header := h2Section[0] - if strings.Index(header, "## ") == 0 { - header = header[3:] + if rest, ok := strings.CutPrefix(header, "## "); ok { + header = rest + } else { + if strings.HasPrefix(header, "---") { + sectionKind = sectionFrontMatter + } else { + h2Section = append([]string{""}, h2Section...) + } + header = "" } - sectionKind := sectionOther - switch header { - case "Timeout", "Timeouts", "User Project Override", "User Project Overrides": + case "Timeout", + "Timeouts", + "User Project Override", + "User Project Overrides": p.sink.debug("Ignoring doc section [%v] for [%v]", header, p.rawname) ignoredDocHeaders[header]++ return nil case "Example Usage", "Example": sectionKind = sectionExampleUsage - case "Arguments Reference", "Argument Reference", "Argument reference", "Nested Blocks", "Nested blocks", "Arguments": + case "Arguments Reference", + "Argument Reference", + "Argument reference", + "Nested Blocks", + "Nested blocks", + "Arguments": sectionKind = sectionArgsReference - case "Attributes Reference", "Attribute Reference", "Attribute reference", "Attributes": + case "Attributes Reference", + "Attribute Reference", + "Attribute reference", + "Attributes": sectionKind = sectionAttributesReference case "Import", "Imports": sectionKind = sectionImports - case "---": - sectionKind = sectionFrontMatter case "Schema": p.parseSchemaWithNestedSections(h2Section) return nil @@ -757,7 +736,7 @@ func (p *tfMarkdownParser) parseSection(h2Section []string) error { } // For all other sections, append them to the description section. - if !wroteHeader { + if !wroteHeader && header != "" { p.ret.Description += fmt.Sprintf("## %s\n", header) wroteHeader = true if !isBlank(reformattedH3Section[0]) { diff --git a/pkg/tfgen/docs_test.go b/pkg/tfgen/docs_test.go index 6c7c7952a5..c19dc5b941 100644 --- a/pkg/tfgen/docs_test.go +++ b/pkg/tfgen/docs_test.go @@ -24,6 +24,7 @@ import ( "os" "path/filepath" "runtime" + "strings" "testing" "text/template" @@ -35,9 +36,13 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/util" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfgen/internal/testprovider" + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfgen/parse" ) var ( @@ -1277,63 +1282,6 @@ content } } -func TestFixExamplesHeaders(t *testing.T) { - codeFence := "```" - t.Run("WithCodeFences", func(t *testing.T) { - markdown := ` -# digitalocean\_cdn - -Provides a DigitalOcean CDN Endpoint resource for use with Spaces. - -## Example Usage - -#### Basic Example - -` + codeFence + `typescript -// Some code. -` + codeFence + ` -## Argument Reference` - - var processedMarkdown string - groups := splitByMarkdownHeaders(markdown, 2) - for _, lines := range groups { - fixExampleTitles(lines) - for _, line := range lines { - processedMarkdown += line - } - } - - assert.NotContains(t, processedMarkdown, "#### Basic Example") - assert.Contains(t, processedMarkdown, "### Basic Example") - }) - - t.Run("WithoutCodeFences", func(t *testing.T) { - markdown := ` -# digitalocean\_cdn - -Provides a DigitalOcean CDN Endpoint resource for use with Spaces. - -## Example Usage - -#### Basic Example - -Misleading example title without any actual code fences. We should not modify the title. - -## Argument Reference` - - var processedMarkdown string - groups := splitByMarkdownHeaders(markdown, 2) - for _, lines := range groups { - fixExampleTitles(lines) - for _, line := range lines { - processedMarkdown += line - } - } - - assert.Contains(t, processedMarkdown, "#### Basic Example") - }) -} - func TestExtractExamples(t *testing.T) { basic := `Previews a CIDR from an IPAM address pool. Only works for private IPv4. @@ -1359,11 +1307,24 @@ Basic usage:` } func TestReformatExamples(t *testing.T) { - runTest := func(input string, expected [][]string) { - inputSections := splitByMarkdownHeaders(input, 2) - actual := reformatExamples(inputSections) + runTest := func(t *testing.T, input string, expected [][]string) { + t.Helper() + var out bytes.Buffer + err := goldmark.New( + goldmark.WithExtensions(parse.TFRegistryExtension), + goldmark.WithParserOptions(parser.WithASTTransformers( + util.Prioritized(exampleTransformer{}, 2000), + )), + goldmark.WithRenderer(parse.RenderMarkdown()), + ).Convert([]byte(input), &out) + require.NoError(t, err) + + exp := make([]string, len(expected)) + for i, e := range expected { + exp[i] = strings.Join(e, "\n") + } - assert.Equal(t, expected, actual) + assertEqualHTML(t, strings.Join(exp, "\n"), out.String()) } // This is a simple use case. We expect no changes to the original doc: @@ -1386,7 +1347,7 @@ example usage content` }, } - runTest(input, expected) + runTest(t, input, expected) }) // This use case demonstrates 2 examples at the same H2 level: a canonical Example @@ -1422,7 +1383,7 @@ specific case content` }, } - runTest(input, expected) + runTest(t, input, expected) }) // This use case demonstrates 2 no canonical Example Usage/basic case and 2 @@ -1458,7 +1419,7 @@ content 2` }, } - runTest(input, expected) + runTest(t, input, expected) }) t.Run("misformatted-docs-dont-panic", func(t *testing.T) { @@ -1476,7 +1437,7 @@ content` }, } - runTest(input, expected) + runTest(t, input, expected) }) } diff --git a/pkg/tfgen/test_data/TestConvertViaPulumiCLI/schema.json b/pkg/tfgen/test_data/TestConvertViaPulumiCLI/schema.json index 90244c7194..ba91e08207 100644 --- a/pkg/tfgen/test_data/TestConvertViaPulumiCLI/schema.json +++ b/pkg/tfgen/test_data/TestConvertViaPulumiCLI/schema.json @@ -22,7 +22,7 @@ }, "resources": { "simple:index:resource": { - "description": "## Example Usage\n\n\u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as simple from \"@pulumi/simple\";\n\nconst aResource = new simple.Resource(\"a_resource\", {\n renamedInput1: \"hello\",\n inputTwo: \"true\",\n});\nexport const someOutput = aResource.result;\n```\n```python\nimport pulumi\nimport pulumi_simple as simple\n\na_resource = simple.Resource(\"a_resource\",\n renamed_input1=\"hello\",\n input_two=\"true\")\npulumi.export(\"someOutput\", a_resource.result)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Simple = Pulumi.Simple;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var aResource = new Simple.Resource(\"a_resource\", new()\n {\n RenamedInput1 = \"hello\",\n InputTwo = \"true\",\n });\n\n return new Dictionary\u003cstring, object?\u003e\n {\n [\"someOutput\"] = aResource.Result,\n };\n});\n```\n```go\npackage main\n\nimport (\n\t\"example.com/pulumi-simple/sdk/go/simple\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\taResource, err := simple.Newresource(ctx, \"a_resource\", \u0026simple.resourceArgs{\n\t\t\tRenamedInput1: pulumi.String(\"hello\"),\n\t\t\tInputTwo: pulumi.String(\"true\"),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tctx.Export(\"someOutput\", aResource.Result)\n\t\treturn nil\n\t})\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.simple.resource;\nimport com.pulumi.simple.ResourceArgs;\nimport java.util.List;\nimport java.util.ArrayList;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var aResource = new Resource(\"aResource\", ResourceArgs.builder()\n .renamedInput1(\"hello\")\n .inputTwo(true)\n .build());\n\n ctx.export(\"someOutput\", aResource.result());\n }\n}\n```\n```yaml\nresources:\n aResource:\n type: simple:resource\n name: a_resource\n properties:\n renamedInput1: hello\n inputTwo: true\noutputs:\n someOutput: ${aResource.result}\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n##Extras\n", + "description": "Sample resource.\n\n## Example Usage\n\n\u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as simple from \"@pulumi/simple\";\n\nconst aResource = new simple.Resource(\"a_resource\", {\n renamedInput1: \"hello\",\n inputTwo: \"true\",\n});\nexport const someOutput = aResource.result;\n```\n```python\nimport pulumi\nimport pulumi_simple as simple\n\na_resource = simple.Resource(\"a_resource\",\n renamed_input1=\"hello\",\n input_two=\"true\")\npulumi.export(\"someOutput\", a_resource.result)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Simple = Pulumi.Simple;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var aResource = new Simple.Resource(\"a_resource\", new()\n {\n RenamedInput1 = \"hello\",\n InputTwo = \"true\",\n });\n\n return new Dictionary\u003cstring, object?\u003e\n {\n [\"someOutput\"] = aResource.Result,\n };\n});\n```\n```go\npackage main\n\nimport (\n\t\"example.com/pulumi-simple/sdk/go/simple\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\taResource, err := simple.Newresource(ctx, \"a_resource\", \u0026simple.resourceArgs{\n\t\t\tRenamedInput1: pulumi.String(\"hello\"),\n\t\t\tInputTwo: pulumi.String(\"true\"),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tctx.Export(\"someOutput\", aResource.Result)\n\t\treturn nil\n\t})\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.simple.resource;\nimport com.pulumi.simple.ResourceArgs;\nimport java.util.List;\nimport java.util.ArrayList;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var aResource = new Resource(\"aResource\", ResourceArgs.builder()\n .renamedInput1(\"hello\")\n .inputTwo(true)\n .build());\n\n ctx.export(\"someOutput\", aResource.result());\n }\n}\n```\n```yaml\nresources:\n aResource:\n type: simple:resource\n name: a_resource\n properties:\n renamedInput1: hello\n inputTwo: true\noutputs:\n someOutput: ${aResource.result}\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n##Extras\n", "properties": { "inputTwo": { "type": "string" diff --git a/pkg/tfgen/test_data/custom-replaces/expected.json b/pkg/tfgen/test_data/custom-replaces/expected.json index 5f8474f435..283f4dd0d3 100644 --- a/pkg/tfgen/test_data/custom-replaces/expected.json +++ b/pkg/tfgen/test_data/custom-replaces/expected.json @@ -1,5 +1,5 @@ { - "Description": "## \n\nThis is a test for checking custom replaces.", + "Description": "This is a test for checking custom replaces.", "Arguments": {}, "Attributes": {}, "Import": "" diff --git a/pkg/tfgen/test_data/link/expected.json b/pkg/tfgen/test_data/link/expected.json index fde9f68e9d..8e9b504c66 100644 --- a/pkg/tfgen/test_data/link/expected.json +++ b/pkg/tfgen/test_data/link/expected.json @@ -1,5 +1,5 @@ { - "Description": "## \n\nThis is a test that we correctly strip TF doc links.\n\nThis is another test that we correctly strip TF import links.\n\nWe also make sure that we strip TF registry doc links.", + "Description": "This is a test that we correctly strip TF doc links.\n\nThis is another test that we correctly strip TF import links.\n\nWe also make sure that we strip TF registry doc links.", "Arguments": {}, "Attributes": {}, "Import": "" diff --git a/pkg/tfgen/test_data/signalfx-log-timeline/expected.json b/pkg/tfgen/test_data/signalfx-log-timeline/expected.json index 3be8db5d2e..64aa998361 100644 --- a/pkg/tfgen/test_data/signalfx-log-timeline/expected.json +++ b/pkg/tfgen/test_data/signalfx-log-timeline/expected.json @@ -14,7 +14,7 @@ "description": "Name of the log timeline." }, "program_text": { - "description": "Signalflow program text for the log timeline. More info at https://dev.splunk.com/observability/docs/." + "description": "Signalflow program text for the log timeline. More info at \u003chttps://dev.splunk.com/observability/docs/\u003e." }, "start_time": { "description": "Seconds since epoch. Used for visualization. Conflicts with `time_range`." diff --git a/pkg/tfgen/test_data/simple/expected.json b/pkg/tfgen/test_data/simple/expected.json index 90499097dd..01a16e8669 100644 --- a/pkg/tfgen/test_data/simple/expected.json +++ b/pkg/tfgen/test_data/simple/expected.json @@ -1,5 +1,5 @@ { - "Description": "## \n\n\t\tThis is a document for the pkg_mod1_res1 resource. To create this resource, run \"pulumi preview\" then \"pulumi up\".\n\t\tThe specified email must be valid: `jdoe@example.com`.\n\n The description will say \"Made by Pulumi\" or \"Made with Pulumi\".", + "Description": "This is a document for the pkg_mod1_res1 resource. To create this resource, run \"pulumi preview\" then \"pulumi up\".\n \tThe specified email must be valid: `jdoe@example.com`.\n\n The description will say \"Made by Pulumi\" or \"Made with Pulumi\".", "Arguments": {}, "Attributes": {}, "Import": ""