Repository: a-h/templ
Pull request #905 (2024-09-06 13:30:41)

feat: enable rawgo experiment as a core feature


Patch:
diff --git a/.version b/.version
index 6d5f174..8cc5642 100644
--- a/.version
+++ b/.version
@@ -1,1 +1,1 @@
-0.2.773
\ No newline at end of file
+0.2.778
\ No newline at end of file
diff --git a/cfg/cfg.go b/cfg/cfg.go
index fcf6103..0ea880d 100644
--- a/cfg/cfg.go
+++ b/cfg/cfg.go
@@ -6,10 +6,7 @@ import (
 	"strings"
 )
 
-type Flags struct {
-	// RawGo will enable the support of arbibrary Go code in templates.
-	RawGo bool
-}
+type Flags struct{}
 
 var Experiment = parse()
 
@@ -19,7 +16,5 @@ func parse() *Flags {
 		m[strings.ToLower(f)] = true
 	}
 
-	return &Flags{
-		RawGo: m["rawgo"],
-	}
+	return &Flags{}
 }
diff --git a/cmd/templ/lspcmd/lspdiff/lspdiff.go b/cmd/templ/lspcmd/lspdiff/lspdiff.go
index af67f86..653ee96 100644
--- a/cmd/templ/lspcmd/lspdiff/lspdiff.go
+++ b/cmd/templ/lspcmd/lspdiff/lspdiff.go
@@ -25,6 +25,10 @@ func CompletionList(expected, actual *protocol.CompletionList) string {
 	)
 }
 
+func References(expected, actual []protocol.Location) string {
+	return cmp.Diff(expected, actual)
+}
+
 func CompletionListContainsText(cl *protocol.CompletionList, text string) bool {
 	if cl == nil {
 		return false
diff --git a/cmd/templ/lspcmd/proxy/server.go b/cmd/templ/lspcmd/proxy/server.go
index 8ac3597..fe66894 100644
--- a/cmd/templ/lspcmd/proxy/server.go
+++ b/cmd/templ/lspcmd/proxy/server.go
@@ -939,7 +939,6 @@ func (p *Server) RangeFormatting(ctx context.Context, params *lsp.DocumentRangeF
 func (p *Server) References(ctx context.Context, params *lsp.ReferenceParams) (result []lsp.Location, err error) {
 	p.Log.Info("client -> server: References")
 	defer p.Log.Info("client -> server: References end")
-	templURI := params.TextDocument.URI
 	// Rewrite the request.
 	var ok bool
 	ok, params.TextDocument.URI, params.Position = p.updatePosition(params.TextDocument.URI, params.Position)
@@ -954,8 +953,12 @@ func (p *Server) References(ctx context.Context, params *lsp.ReferenceParams) (r
 	// Rewrite the response.
 	for i := 0; i < len(result); i++ {
 		r := result[i]
-		r.URI = templURI
-		r.Range = p.convertGoRangeToTemplRange(templURI, r.Range)
+		isTemplURI, templURI := convertTemplGoToTemplURI(r.URI)
+		if isTemplURI {
+			p.Log.Info(fmt.Sprintf("references-%d - range conversion for %s", i, r.URI))
+			r.URI, r.Range = templURI, p.convertGoRangeToTemplRange(templURI, r.Range)
+		}
+		p.Log.Info(fmt.Sprintf("references-%d: %+v", i, r))
 		result[i] = r
 	}
 	return result, err
diff --git a/docs/docs/03-syntax-and-usage/09-raw-go.md b/docs/docs/03-syntax-and-usage/09-raw-go.md
index 428e85b..56302db 100644
--- a/docs/docs/03-syntax-and-usage/09-raw-go.md
+++ b/docs/docs/03-syntax-and-usage/09-raw-go.md
@@ -1,13 +1,5 @@
 # Raw Go
 
-:::caution
-This page describes functionality that is experimental, not enabled by default, and may change or be removed in future versions.
-
-To enable this feature run the generation step with the `rawgo` experiment flag: `TEMPL_EXPERIMENT=rawgo templ generate`
-
-You will also need to set the `TEMPL_EXPERIMENT=rawgo` environment variable at your system level or within your editor to enable LSP behavior.
-:::
-
 For some more advanced use cases it may be useful to write Go code statements in your template.
 
 Use the `{{ ... }}` syntax for this.
diff --git a/docs/docs/03-syntax-and-usage/10-template-composition.md b/docs/docs/03-syntax-and-usage/10-template-composition.md
index cd869b6..e9d0f3b 100644
--- a/docs/docs/03-syntax-and-usage/10-template-composition.md
+++ b/docs/docs/03-syntax-and-usage/10-template-composition.md
@@ -64,6 +64,81 @@ The use of the `{ children... }` expression in the child component.
 </div>
 ```
 
+### Using children in code components
+
+Children are passed to a component using the Go context. To pass children to a component using Go code, use the `templ.WithChildren` function.
+
+```templ
+package main
+
+import (
+  "context"
+  "os"
+
+  "github.com/a-h/templ"
+)
+
+templ wrapChildren() {
+	<div id="wrapper">
+		{ children... }
+	</div>
+}
+
+func main() {
+  contents := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
+    _, err := io.WriteString(w, "<div>Inserted from Go code</div>")
+    return err
+  })
+  ctx := templ.WithChildren(context.Background(), contents)
+  wrapChildren().Render(ctx, os.Stdout)
+}
+```
+
+```html title="output"
+<div id="wrapper">
+ <div>
+  Inserted from Go code
+ </div>
+</div>
+```
+
+To get children from the context, use the `templ.GetChildren` function.
+
+```templ
+package main
+
+import (
+  "context"
+  "os"
+
+  "github.com/a-h/templ"
+)
+
+func main() {
+  contents := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
+    _, err := io.WriteString(w, "<div>Inserted from Go code</div>")
+    return err
+  })
+  wrapChildren := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
+    children := templ.GetChildren(ctx)
+    ctx = templ.ClearChildren(ctx)
+    _, err := io.WriteString(w, "<div id=\"wrapper\">")
+    if err != nil {
+      return err
+    }
+    err = children.Render(ctx, w)
+    if err != nil {
+      return err
+    }
+    _, err = io.WriteString(w, "</div>")
+    return err
+  })
+```
+
+:::note
+The `templ.ClearChildren` function is used to stop passing the children down the tree.
+:::
+
 ## Components as parameters
 
 Components can also be passed as parameters and rendered using the `@component` expression.
diff --git a/parser/v2/gocodeparser.go b/parser/v2/gocodeparser.go
index 36ca686..027bcc4 100644
--- a/parser/v2/gocodeparser.go
+++ b/parser/v2/gocodeparser.go
@@ -2,14 +2,10 @@ package parser
 
 import (
 	"github.com/a-h/parse"
-	"github.com/a-h/templ/cfg"
 	"github.com/a-h/templ/parser/v2/goexpression"
 )
 
 var goCode = parse.Func(func(pi *parse.Input) (n Node, ok bool, err error) {
-	if !cfg.Experiment.RawGo {
-		return
-	}
 	// Check the prefix first.
 	if _, ok, err = parse.Or(parse.String("{{ "), parse.String("{{")).Parse(pi); err != nil || !ok {
 		return


Test patch:
diff --git a/cmd/templ/lspcmd/lsp_test.go b/cmd/templ/lspcmd/lsp_test.go
index 6e9f914..2331e49 100644
--- a/cmd/templ/lspcmd/lsp_test.go
+++ b/cmd/templ/lspcmd/lsp_test.go
@@ -319,6 +319,142 @@ func TestHover(t *testing.T) {
 	}
 }
 
+func TestReferences(t *testing.T) {
+	if testing.Short() {
+		return
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+	log, _ := zap.NewProduction()
+
+	ctx, appDir, _, server, teardown, err := Setup(ctx, log)
+	if err != nil {
+		t.Fatalf("failed to setup test: %v", err)
+		return
+	}
+	defer teardown(t)
+	defer cancel()
+
+	templFile, err := os.ReadFile(appDir + "/templates.templ")
+	if err != nil {
+		t.Fatalf("failed to read file %q: %v", appDir+"/templates.templ", err)
+		return
+
+	}
+	err = server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
+		TextDocument: protocol.TextDocumentItem{
+			URI:        uri.URI("file://" + appDir + "/templates.templ"),
+			LanguageID: "templ",
+			Version:    1,
+			Text:       string(templFile),
+		},
+	})
+	if err != nil {
+		t.Errorf("failed to register open file: %v", err)
+		return
+	}
+	log.Info("Calling References")
+
+	tests := []struct {
+		line      int
+		character int
+		assert    func(t *testing.T, l []protocol.Location) (msg string, ok bool)
+	}{
+		{
+			// this is the definition of the templ function in the templates.templ file.
+			line:      5,
+			character: 9,
+			assert: func(t *testing.T, actual []protocol.Location) (msg string, ok bool) {
+				expectedReference := []protocol.Location{
+					{
+						// This is the useage of the templ function in the main.go file.
+						URI: uri.URI("file://" + appDir + "/main.go"),
+						Range: protocol.Range{
+							Start: protocol.Position{
+								Line:      uint32(24),
+								Character: uint32(7),
+							},
+							End: protocol.Position{
+								Line:      uint32(24),
+								Character: uint32(11),
+							},
+						},
+					},
+				}
+				if diff := lspdiff.References(expectedReference, actual); diff != "" {
+					return fmt.Sprintf("Expected: %+v\nActual: %+v", expectedReference, actual), false
+				}
+				return "", true
+			},
+		},
+		{
+			// this is the definition of the struct in the templates.templ file.
+			line:      21,
+			character: 9,
+			assert: func(t *testing.T, actual []protocol.Location) (msg string, ok bool) {
+				expectedReference := []protocol.Location{
+					{
+						// This is the useage of the struct in the templates.templ file.
+						URI: uri.URI("file://" + appDir + "/templates.templ"),
+						Range: protocol.Range{
+							Start: protocol.Position{
+								Line:      uint32(24),
+								Character: uint32(8),
+							},
+							End: protocol.Position{
+								Line:      uint32(24),
+								Character: uint32(14),
+							},
+						},
+					},
+				}
+				if diff := lspdiff.References(expectedReference, actual); diff != "" {
+					return fmt.Sprintf("Expected: %+v\nActual: %+v", expectedReference, actual), false
+				}
+				return "", true
+			},
+		},
+	}
+
+	for i, test := range tests {
+		t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
+			// Give CI/CD pipeline executors some time because they're often quite slow.
+			var ok bool
+			var msg string
+			for i := 0; i < 3; i++ {
+				if err != nil {
+					t.Error(err)
+					return
+				}
+				actual, err := server.References(ctx, &protocol.ReferenceParams{
+					TextDocumentPositionParams: protocol.TextDocumentPositionParams{
+						TextDocument: protocol.TextDocumentIdentifier{
+							URI: uri.URI("file://" + appDir + "/templates.templ"),
+						},
+						// Positions are zero indexed.
+						Position: protocol.Position{
+							Line:      uint32(test.line - 1),
+							Character: uint32(test.character - 1),
+						},
+					},
+				})
+				if err != nil {
+					t.Errorf("failed to get references: %v", err)
+					return
+				}
+				msg, ok = test.assert(t, actual)
+				if !ok {
+					break
+				}
+				time.Sleep(time.Millisecond * 500)
+			}
+			if !ok {
+				t.Error(msg)
+			}
+		})
+	}
+}
+
 func TestCodeAction(t *testing.T) {
 	if testing.Short() {
 		return
diff --git a/parser/v2/format_test.go b/parser/v2/format_test.go
index bd96a3e..3c6ee1e 100644
--- a/parser/v2/format_test.go
+++ b/parser/v2/format_test.go
@@ -5,17 +5,11 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/a-h/templ/cfg"
 	"github.com/google/go-cmp/cmp"
 	"golang.org/x/tools/txtar"
 )
 
 func TestFormatting(t *testing.T) {
-	oldFlags := cfg.Experiment
-	t.Cleanup(func() {
-		cfg.Experiment = oldFlags
-	})
-	cfg.Experiment.RawGo = true
 	files, _ := filepath.Glob("formattestdata/*.txt")
 	if len(files) == 0 {
 		t.Errorf("no test files found")
diff --git a/parser/v2/gocodeparser_test.go b/parser/v2/gocodeparser_test.go
index 19fada8..af3bd04 100644
--- a/parser/v2/gocodeparser_test.go
+++ b/parser/v2/gocodeparser_test.go
@@ -4,17 +4,10 @@ import (
 	"testing"
 
 	"github.com/a-h/parse"
-	"github.com/a-h/templ/cfg"
 	"github.com/google/go-cmp/cmp"
 )
 
 func TestGoCodeParser(t *testing.T) {
-	flagVal := cfg.Experiment.RawGo
-	cfg.Experiment.RawGo = true
-	defer func() {
-		cfg.Experiment.RawGo = flagVal
-	}()
-
 	tests := []struct {
 		name     string
 		input    string