diff --git a/.version b/.version index bceeec9b3..a7990c86c 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -0.2.648 \ No newline at end of file +0.2.651 \ No newline at end of file diff --git a/cmd/templ/fmtcmd/main.go b/cmd/templ/fmtcmd/main.go index e460f2e02..98dcb1b55 100644 --- a/cmd/templ/fmtcmd/main.go +++ b/cmd/templ/fmtcmd/main.go @@ -144,7 +144,8 @@ func format(write writer, read reader) (err error) { return err } w := new(bytes.Buffer) - if err = t.Write(w); err != nil { + cw := parser.NewContextWriter(w, parser.WriteContextAll) + if err = t.Write(cw); err != nil { return fmt.Errorf("formatting error: %w", err) } return write(fileName, w.String()) diff --git a/cmd/templ/lspcmd/proxy/server.go b/cmd/templ/lspcmd/proxy/server.go index 662b689e2..72daf66ed 100644 --- a/cmd/templ/lspcmd/proxy/server.go +++ b/cmd/templ/lspcmd/proxy/server.go @@ -734,7 +734,8 @@ func (p *Server) Formatting(ctx context.Context, params *lsp.DocumentFormattingP return } w := new(strings.Builder) - err = template.Write(w) + cw := parser.NewContextWriter(w, parser.WriteContextAll) + err = template.Write(cw) if err != nil { p.Log.Error("handleFormatting: faled to write template", zap.Error(err)) return diff --git a/cmd/templ/migratecmd/main.go b/cmd/templ/migratecmd/main.go index bf6e9fbb3..21a5d2de5 100644 --- a/cmd/templ/migratecmd/main.go +++ b/cmd/templ/migratecmd/main.go @@ -94,7 +94,8 @@ func migrate(fileName string) (err error) { // Write the updated file. w := new(bytes.Buffer) - err = v2Template.Write(w) + cw := v2.NewContextWriter(w, v2.WriteContextAll) + err = v2Template.Write(cw) if err != nil { return fmt.Errorf("%s formatting error: %w", fileName, err) } diff --git a/parser/v2/cssparser_test.go b/parser/v2/cssparser_test.go index 851608ff3..c241bbeda 100644 --- a/parser/v2/cssparser_test.go +++ b/parser/v2/cssparser_test.go @@ -1,6 +1,7 @@ package parser import ( + "bytes" "testing" "github.com/a-h/parse" @@ -9,13 +10,15 @@ import ( func TestExpressionCSSPropertyParser(t *testing.T) { tests := []struct { - name string - input string - expected ExpressionCSSProperty + name string + input string + expected ExpressionCSSProperty + expectedCSS string }{ { - name: "css: single constant property", - input: `background-color: { constants.BackgroundColor };`, + name: "css: single constant property", + input: `background-color: { constants.BackgroundColor };`, + expectedCSS: "background-color: ' ';\n", expected: ExpressionCSSProperty{ Name: "background-color", Value: StringExpression{ @@ -38,8 +41,9 @@ func TestExpressionCSSPropertyParser(t *testing.T) { }, }, { - name: "css: single constant property with windows newlines", - input: "background-color:\r\n{ constants.BackgroundColor };\r\n", + name: "css: single constant property with windows newlines", + input: "background-color:\r\n{ constants.BackgroundColor };\r\n", + expectedCSS: "background-color: ' ';\n", expected: ExpressionCSSProperty{ Name: "background-color", Value: StringExpression{ @@ -76,27 +80,47 @@ func TestExpressionCSSPropertyParser(t *testing.T) { if diff := cmp.Diff(tt.expected, result); diff != "" { t.Errorf(diff) } + + w := new(bytes.Buffer) + cw := NewContextWriter(w, WriteContextCSS) + if err := result.Write(cw, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + actualCSS := w.String() + if diff := cmp.Diff(tt.expectedCSS, actualCSS); diff != "" { + t.Error(diff) + + t.Errorf("input:\n%s", displayWhitespaceChars(tt.input)) + t.Errorf("expected:\n%s", displayWhitespaceChars(tt.expectedCSS)) + t.Errorf("got:\n%s", displayWhitespaceChars(actualCSS)) + } + if diff := cmp.Diff(getLineLengths(tt.input), getLineLengths(actualCSS)); diff != "" { + t.Errorf(diff) + } }) } } func TestConstantCSSPropertyParser(t *testing.T) { tests := []struct { - name string - input string - expected ConstantCSSProperty + name string + input string + expected ConstantCSSProperty + expectedCSS string }{ { - name: "css: single constant property", - input: `background-color: #ffffff;`, + name: "css: single constant property", + input: `background-color: #ffffff;`, + expectedCSS: "background-color: #ffffff;\n", expected: ConstantCSSProperty{ Name: "background-color", Value: "#ffffff", }, }, { - name: "css: single constant webkit property", - input: `-webkit-text-stroke-color: #ffffff;`, + name: "css: single constant webkit property", + input: `-webkit-text-stroke-color: #ffffff;`, + expectedCSS: "-webkit-text-stroke-color: #ffffff;\n", expected: ConstantCSSProperty{ Name: "-webkit-text-stroke-color", Value: "#ffffff", @@ -117,20 +141,40 @@ func TestConstantCSSPropertyParser(t *testing.T) { if diff := cmp.Diff(tt.expected, result); diff != "" { t.Errorf(diff) } + + w := new(bytes.Buffer) + cw := NewContextWriter(w, WriteContextCSS) + if err := result.Write(cw, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + actualCSS := w.String() + if diff := cmp.Diff(tt.expectedCSS, actualCSS); diff != "" { + t.Error(diff) + + t.Errorf("input:\n%s", displayWhitespaceChars(tt.input)) + t.Errorf("expected:\n%s", displayWhitespaceChars(tt.expectedCSS)) + t.Errorf("got:\n%s", displayWhitespaceChars(actualCSS)) + } + if diff := cmp.Diff(getLineLengths(tt.input), getLineLengths(actualCSS)); diff != "" { + t.Errorf(diff) + } }) } } func TestCSSParser(t *testing.T) { tests := []struct { - name string - input string - expected CSSTemplate + name string + input string + expected CSSTemplate + expectedCSS string }{ { name: "css: no parameters, no content", input: `css Name() { }`, + expectedCSS: ` + `, expected: CSSTemplate{ Name: "Name", Expression: Expression{ @@ -155,6 +199,8 @@ func TestCSSParser(t *testing.T) { name: "css: without spaces", input: `css Name() { }`, + expectedCSS: ` + `, expected: CSSTemplate{ Name: "Name", Expression: Expression{ @@ -180,6 +226,9 @@ func TestCSSParser(t *testing.T) { input: `css Name() { background-color: #ffffff; }`, + expectedCSS: ` + background-color: #ffffff; + `, expected: CSSTemplate{ Name: "Name", Expression: Expression{ @@ -210,6 +259,9 @@ background-color: #ffffff; input: `css Name() { background-color: { constants.BackgroundColor }; }`, + expectedCSS: ` + background-color: ' '; + `, expected: CSSTemplate{ Name: "Name", Expression: Expression{ @@ -256,6 +308,9 @@ background-color: { constants.BackgroundColor }; input: `css Name(prop string) { background-color: { prop }; }`, + expectedCSS: ` + background-color: ' '; + `, expected: CSSTemplate{ Name: "Name", Expression: Expression{ @@ -312,6 +367,23 @@ background-color: { prop }; if diff := cmp.Diff(tt.expected, result); diff != "" { t.Errorf(diff) } + + w := new(bytes.Buffer) + cw := NewContextWriter(w, WriteContextCSS) + if err := result.Write(cw, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + actualCSS := w.String() + if diff := cmp.Diff(tt.expectedCSS, actualCSS); diff != "" { + t.Error(diff) + + t.Errorf("input:\n%s", displayWhitespaceChars(tt.input)) + t.Errorf("expected:\n%s", displayWhitespaceChars(tt.expectedCSS)) + t.Errorf("got:\n%s", displayWhitespaceChars(actualCSS)) + } + if diff := cmp.Diff(getLineLengths(tt.input), getLineLengths(actualCSS)); diff != "" { + t.Errorf(diff) + } }) } } diff --git a/parser/v2/elementparser_test.go b/parser/v2/elementparser_test.go index 6bb695aa3..a54d9fb50 100644 --- a/parser/v2/elementparser_test.go +++ b/parser/v2/elementparser_test.go @@ -1,6 +1,7 @@ package parser import ( + "bytes" "strings" "testing" @@ -552,13 +553,15 @@ if test { func TestElementParser(t *testing.T) { tests := []struct { - name string - input string - expected Element + name string + input string + expected Element + expectedHTML string }{ { - name: "element: self-closing with single constant attribute", - input: ``, + name: "element: self-closing with single constant attribute", + input: ``, + expectedHTML: ``, expected: Element{ Name: "a", NameRange: Range{ @@ -578,8 +581,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: self-closing with single bool expression attribute", - input: `
`, + name: "element: self-closing with single bool expression attribute", + input: `
`, + expectedHTML: `
`, expected: Element{ Name: "hr", NameRange: Range{ @@ -613,8 +617,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: self-closing with single expression attribute", - input: ``, + name: "element: self-closing with single expression attribute", + input: ``, + expectedHTML: ``, expected: Element{ Name: "a", NameRange: Range{ @@ -648,8 +653,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: self-closing with multiple constant attributes", - input: ``, + name: "element: self-closing with multiple constant attributes", + input: ``, + expectedHTML: ``, expected: Element{ Name: "a", NameRange: Range{ @@ -677,8 +683,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: self-closing with multiple spreads attributes", - input: ``, + name: "element: self-closing with multiple spreads attributes", + input: ``, + expectedHTML: ``, expected: Element{ Name: "a", NameRange: Range{ @@ -724,8 +731,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: self-closing with multiple boolean attributes", - input: `
`, + name: "element: self-closing with multiple boolean attributes", + input: `
`, + expectedHTML: `
`, expected: Element{ Name: "hr", NameRange: Range{ @@ -774,8 +782,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: self-closing with multiple constant and expr attributes", - input: ``, + name: "element: self-closing with multiple constant and expr attributes", + input: ``, + expectedHTML: ``, expected: Element{ Name: "a", NameRange: Range{ @@ -831,9 +840,13 @@ func TestElementParser(t *testing.T) { class="important" } >Test -} - `, + expectedHTML: `
Test
`, expected: Element{ Name: "div", NameRange: Range{ @@ -887,8 +900,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: self-closing with no attributes", - input: `
`, + name: "element: self-closing with no attributes", + input: `
`, + expectedHTML: `
`, expected: Element{ Name: "hr", NameRange: Range{ @@ -898,8 +912,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: self-closing with attribute", - input: `
`, + name: "element: self-closing with attribute", + input: `
`, + expectedHTML: `
`, expected: Element{ Name: "hr", NameRange: Range{ @@ -924,6 +939,12 @@ func TestElementParser(t *testing.T) { if true { class="itIsTrue" } +/>`, + expectedHTML: `
`, expected: Element{ Name: "hr", @@ -979,6 +1000,14 @@ func TestElementParser(t *testing.T) { } else { class="itIsNotTrue" } +/>`, + expectedHTML: `
`, expected: Element{ Name: "hr", @@ -1042,6 +1071,12 @@ func TestElementParser(t *testing.T) { if true { class="itIsTrue" } +>Test

`, + expectedHTML: `

Test

`, expected: Element{ Name: "p", @@ -1093,8 +1128,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: open and close", - input: ``, + name: "element: open and close", + input: ``, + expectedHTML: ``, expected: Element{ Name: "a", NameRange: Range{ @@ -1104,8 +1140,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: open and close with text", - input: `The text`, + name: "element: open and close with text", + input: `The text`, + expectedHTML: `The text`, expected: Element{ Name: "a", NameRange: Range{ @@ -1120,8 +1157,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: with self-closing child element", - input: ``, + name: "element: with self-closing child element", + input: ``, + expectedHTML: ``, expected: Element{ Name: "a", NameRange: Range{ @@ -1140,8 +1178,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: with non-self-closing child element", - input: ``, + name: "element: with non-self-closing child element", + input: ``, + expectedHTML: ``, expected: Element{ Name: "a", NameRange: Range{ @@ -1160,8 +1199,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: containing space", - input: ` `, + name: "element: containing space", + input: ` `, + expectedHTML: ` `, expected: Element{ Name: "a", NameRange: Range{ @@ -1186,8 +1226,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: with multiple child elements", - input: ``, + name: "element: with multiple child elements", + input: ``, + expectedHTML: ``, expected: Element{ Name: "a", NameRange: Range{ @@ -1222,8 +1263,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: empty", - input: `
`, + name: "element: empty", + input: `
`, + expectedHTML: `
`, expected: Element{ Name: "div", NameRange: Range{ @@ -1233,8 +1275,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: containing string expression", - input: `
{ "test" }
`, + name: "element: containing string expression", + input: `
{ "test" }
`, + expectedHTML: `
`, expected: Element{ Name: "div", NameRange: Range{ @@ -1263,8 +1306,9 @@ func TestElementParser(t *testing.T) { }, }, { - name: "element: inputs can contain class attributes", - input: ``, + name: "element: inputs can contain class attributes", + input: ``, + expectedHTML: ``, expected: Element{ Name: "input", NameRange: Range{ @@ -1344,6 +1388,11 @@ func TestElementParser(t *testing.T) { id="email" name="email" >`, + expectedHTML: ``, expected: Element{ Name: "input", IndentAttrs: true, @@ -1394,6 +1443,23 @@ func TestElementParser(t *testing.T) { if diff := cmp.Diff(tt.expected, result); diff != "" { t.Errorf(diff) } + + w := new(bytes.Buffer) + cw := NewContextWriter(w, WriteContextHTML) + if err := result.Write(cw, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + actualHTML := w.String() + if diff := cmp.Diff(tt.expectedHTML, actualHTML); diff != "" { + t.Error(diff) + + t.Errorf("input:\n%s", displayWhitespaceChars(tt.input)) + t.Errorf("expected:\n%s", displayWhitespaceChars(tt.expectedHTML)) + t.Errorf("got:\n%s", displayWhitespaceChars(actualHTML)) + } + if diff := cmp.Diff(getLineLengths(tt.input), getLineLengths(actualHTML)); diff != "" { + t.Errorf(diff) + } }) } } @@ -1512,3 +1578,12 @@ func TestBigElement(t *testing.T) { t.Errorf("unexpected failure to parse") } } + +func getLineLengths(s string) (lengths []int) { + lines := strings.Split(s, "\n") + lengths = make([]int, len(lines)) + for i := 0; i < len(lines); i++ { + lengths[i] = len(lines[i]) + } + return lengths +} diff --git a/parser/v2/forexpressionparser_test.go b/parser/v2/forexpressionparser_test.go index 793bb6f3f..db2c9d3a9 100644 --- a/parser/v2/forexpressionparser_test.go +++ b/parser/v2/forexpressionparser_test.go @@ -1,6 +1,7 @@ package parser import ( + "bytes" "testing" "github.com/a-h/parse" @@ -9,15 +10,19 @@ import ( func TestForExpressionParser(t *testing.T) { tests := []struct { - name string - input string - expected interface{} + name string + input string + expected interface{} + expectedHTML string }{ { name: "for: simple", input: `for _, item := range p.Items {
{ item }
}`, + expectedHTML: ` +
+ `, expected: ForExpression{ Expression: Expression{ Value: `_, item := range p.Items`, @@ -71,6 +76,9 @@ func TestForExpressionParser(t *testing.T) { input: `for _, item := range p.Items{
{ item }
}`, + expectedHTML: ` +
+ `, expected: ForExpression{ Expression: Expression{ Value: `_, item := range p.Items`, @@ -134,6 +142,23 @@ func TestForExpressionParser(t *testing.T) { if diff := cmp.Diff(tt.expected, actual); diff != "" { t.Error(diff) } + + w := new(bytes.Buffer) + cw := NewContextWriter(w, WriteContextHTML) + if err := actual.Write(cw, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + actualHTML := w.String() + if diff := cmp.Diff(tt.expectedHTML, actualHTML); diff != "" { + t.Error(diff) + + t.Errorf("input:\n%s", displayWhitespaceChars(tt.input)) + t.Errorf("expected:\n%s", displayWhitespaceChars(tt.expectedHTML)) + t.Errorf("got:\n%s", displayWhitespaceChars(actualHTML)) + } + if diff := cmp.Diff(getLineLengths(tt.input), getLineLengths(actualHTML)); diff != "" { + t.Errorf(diff) + } }) } } diff --git a/parser/v2/format_test.go b/parser/v2/format_test.go index 64b69f1c3..5036a6d3a 100644 --- a/parser/v2/format_test.go +++ b/parser/v2/format_test.go @@ -28,7 +28,8 @@ func TestFormat(t *testing.T) { t.Fatal(err) } var actual bytes.Buffer - if err := tem.Write(&actual); err != nil { + cw := NewContextWriter(&actual, WriteContextAll) + if err := tem.Write(cw); err != nil { t.Fatalf("unexpected error: %v", err) } if diff := cmp.Diff(string(a.Files[1].Data), actual.String()); diff != "" { diff --git a/parser/v2/htmlcommentparser_test.go b/parser/v2/htmlcommentparser_test.go index 735a4820c..3924798d7 100644 --- a/parser/v2/htmlcommentparser_test.go +++ b/parser/v2/htmlcommentparser_test.go @@ -1,6 +1,7 @@ package parser import ( + "bytes" "testing" "github.com/a-h/parse" @@ -67,6 +68,19 @@ func TestHTMLCommentParser(t *testing.T) { if diff := cmp.Diff(tt.expected, result); diff != "" { t.Errorf(diff) } + + w := new(bytes.Buffer) + cw := NewContextWriter(w, WriteContextHTML) + if err := result.Write(cw, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + actualHTML := w.String() + if diff := cmp.Diff(tt.input, actualHTML); diff != "" { + t.Error(diff) + + t.Errorf("expected:\n%s", displayWhitespaceChars(tt.input)) + t.Errorf("got:\n%s", displayWhitespaceChars(actualHTML)) + } }) } } diff --git a/parser/v2/ifexpressionparser_test.go b/parser/v2/ifexpressionparser_test.go index de3f3fcd8..3b5f52f18 100644 --- a/parser/v2/ifexpressionparser_test.go +++ b/parser/v2/ifexpressionparser_test.go @@ -1,6 +1,7 @@ package parser import ( + "bytes" "testing" "github.com/a-h/parse" @@ -9,9 +10,10 @@ import ( func TestIfExpression(t *testing.T) { tests := []struct { - name string - input string - expected IfExpression + name string + input string + expected IfExpression + expectedHTML string }{ { name: "if: simple expression", @@ -21,6 +23,11 @@ func TestIfExpression(t *testing.T) { } `, + expectedHTML: ` + + + + `, expected: IfExpression{ Expression: Expression{ Value: `p.Test`, @@ -607,6 +614,25 @@ func TestIfExpression(t *testing.T) { if diff := cmp.Diff(tt.expected, actual); diff != "" { t.Error(diff) } + + if tt.expectedHTML != "" { + w := new(bytes.Buffer) + cw := NewContextWriter(w, WriteContextHTML) + if err := actual.Write(cw, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + actualHTML := w.String() + if diff := cmp.Diff(tt.expectedHTML, actualHTML); diff != "" { + t.Error(diff) + + t.Errorf("input:\n%s", displayWhitespaceChars(tt.input)) + t.Errorf("expected:\n%s", displayWhitespaceChars(tt.expectedHTML)) + t.Errorf("got:\n%s", displayWhitespaceChars(actualHTML)) + } + if diff := cmp.Diff(getLineLengths(tt.input), getLineLengths(actualHTML)); diff != "" { + t.Errorf(diff) + } + } }) } } diff --git a/parser/v2/scripttemplateparser_test.go b/parser/v2/scripttemplateparser_test.go index fae590f5d..e0fc00700 100644 --- a/parser/v2/scripttemplateparser_test.go +++ b/parser/v2/scripttemplateparser_test.go @@ -1,6 +1,7 @@ package parser import ( + "bytes" "fmt" "testing" @@ -10,14 +11,17 @@ import ( func TestScriptTemplateParser(t *testing.T) { var tests = []struct { - name string - input string - expected ScriptTemplate + name string + input string + expected ScriptTemplate + expectedJS string }{ { name: "script: no parameters, no content", input: `script Name() { }`, + expectedJS: ` + `, expected: ScriptTemplate{ Name: Expression{ Value: "Name", @@ -55,6 +59,8 @@ func TestScriptTemplateParser(t *testing.T) { name: "script: no spaces", input: `script Name(){ }`, + expectedJS: ` + `, expected: ScriptTemplate{ Name: Expression{ Value: "Name", @@ -93,6 +99,9 @@ func TestScriptTemplateParser(t *testing.T) { input: `script Name() { var x = "x"; }`, + expectedJS: ` +var x = "x"; + `, expected: ScriptTemplate{ Name: Expression{ Value: "Name", @@ -132,6 +141,9 @@ var x = "x"; input: `script Name(value string) { console.log(value); }`, + expectedJS: ` +console.log(value); + `, expected: ScriptTemplate{ Name: Expression{ Value: "Name", @@ -171,6 +183,9 @@ console.log(value); input: `script Name() { //' }`, + expectedJS: ` + //' + `, expected: ScriptTemplate{ Name: Expression{ Value: "Name", @@ -210,6 +225,9 @@ console.log(value); input: `script Name() { let x = ''; }`, + expectedJS: ` + let x = ''; + `, expected: ScriptTemplate{ Name: Expression{ Value: "Name", @@ -266,6 +284,23 @@ console.log(value); t.Error("unexpected suffix") t.Error(diff) } + + w := new(bytes.Buffer) + cw := NewContextWriter(w, WriteContextJS) + if err := actual.Write(cw, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + actualJS := w.String() + if diff := cmp.Diff(tt.expectedJS, actualJS); diff != "" { + t.Error(diff) + + t.Errorf("input:\n%s", displayWhitespaceChars(tt.input)) + t.Errorf("expected:\n%s", displayWhitespaceChars(tt.expectedJS)) + t.Errorf("got:\n%s", displayWhitespaceChars(actualJS)) + } + if diff := cmp.Diff(getLineLengths(tt.input), getLineLengths(actualJS)); diff != "" { + t.Errorf(diff) + } }) } } diff --git a/parser/v2/switchexpressionparser_test.go b/parser/v2/switchexpressionparser_test.go index e17cae717..ca524bd7a 100644 --- a/parser/v2/switchexpressionparser_test.go +++ b/parser/v2/switchexpressionparser_test.go @@ -1,6 +1,7 @@ package parser import ( + "bytes" "testing" "github.com/a-h/parse" @@ -9,14 +10,17 @@ import ( func TestSwitchExpressionParser(t *testing.T) { tests := []struct { - name string - input string - expected SwitchExpression + name string + input string + expected SwitchExpression + expectedHTML string }{ { name: "switch: simple", input: `switch "stringy" { }`, + expectedHTML: ` + `, expected: SwitchExpression{ Expression: Expression{ Value: `"stringy"`, @@ -43,6 +47,12 @@ default: { "span content" } }`, + expectedHTML: ` + + + + + `, expected: SwitchExpression{ Expression: Expression{ Value: `"stringy"`, @@ -121,6 +131,12 @@ default: { "span content" } }`, + expectedHTML: ` + + + + + `, expected: SwitchExpression{ Expression: Expression{ Value: `"stringy"`, @@ -198,6 +214,12 @@ default: case "b": { "B" } }`, + expectedHTML: ` + + + + + `, expected: SwitchExpression{ Expression: Expression{ Value: `"stringy"`, @@ -314,6 +336,23 @@ default: if diff := cmp.Diff(tt.expected, actual); diff != "" { t.Error(diff) } + + w := new(bytes.Buffer) + cw := NewContextWriter(w, WriteContextHTML) + if err := actual.Write(cw, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + actualHTML := w.String() + if diff := cmp.Diff(tt.expectedHTML, actualHTML); diff != "" { + t.Error(diff) + + t.Errorf("input:\n%s", displayWhitespaceChars(tt.input)) + t.Errorf("expected:\n%s", displayWhitespaceChars(tt.expectedHTML)) + t.Errorf("got:\n%s", displayWhitespaceChars(actualHTML)) + } + if diff := cmp.Diff(getLineLengths(tt.input), getLineLengths(actualHTML)); diff != "" { + t.Errorf(diff) + } }) } } diff --git a/parser/v2/templatefile_test.go b/parser/v2/templatefile_test.go index cd0b91aa9..457cdedab 100644 --- a/parser/v2/templatefile_test.go +++ b/parser/v2/templatefile_test.go @@ -291,7 +291,8 @@ templ Hello() { } sb := new(strings.Builder) - err = tf.Write(sb) + cw := NewContextWriter(sb, WriteContextAll) + err = tf.Write(cw) if err != nil { t.Fatalf("failed to write template file: %v", err) } diff --git a/parser/v2/templateparser_test.go b/parser/v2/templateparser_test.go index 56caec5a7..db9492af6 100644 --- a/parser/v2/templateparser_test.go +++ b/parser/v2/templateparser_test.go @@ -1,6 +1,7 @@ package parser import ( + "bytes" "testing" "github.com/a-h/parse" @@ -9,10 +10,11 @@ import ( func TestTemplateParser(t *testing.T) { var tests = []struct { - name string - input string - expected HTMLTemplate - expectError bool + name string + input string + expected HTMLTemplate + expectedHTML string + expectError bool }{ { name: "template: no parameters", @@ -131,6 +133,9 @@ func TestTemplateParser(t *testing.T) { input: `templ Name(p Parameter) { { "span content" } }`, + expectedHTML: ` + + `, expected: HTMLTemplate{ Expression: Expression{ Value: "Name(p Parameter)", @@ -749,6 +754,24 @@ func TestTemplateParser(t *testing.T) { case !tt.expectError && diff != "": t.Errorf(diff) } + if tt.expectedHTML != "" { + w := new(bytes.Buffer) + cw := NewContextWriter(w, WriteContextHTML) + if err := actual.Write(cw, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + actualHTML := w.String() + if diff := cmp.Diff(tt.expectedHTML, actualHTML); diff != "" { + t.Error(diff) + + t.Errorf("input:\n%s", displayWhitespaceChars(tt.input)) + t.Errorf("expected:\n%s", displayWhitespaceChars(tt.expectedHTML)) + t.Errorf("got:\n%s", displayWhitespaceChars(actualHTML)) + } + if diff := cmp.Diff(getLineLengths(tt.input), getLineLengths(actualHTML)); diff != "" { + t.Errorf(diff) + } + } }) } } diff --git a/parser/v2/types.go b/parser/v2/types.go index 7ff842b24..6ab501874 100644 --- a/parser/v2/types.go +++ b/parser/v2/types.go @@ -46,6 +46,53 @@ import ( // // } +type WriteContext int + +func (wc WriteContext) IsSet(flag WriteContext) bool { + return wc&flag == flag +} + +const WriteContextGo WriteContext = 0b0001 +const WriteContextHTML WriteContext = 0b0010 +const WriteContextCSS WriteContext = 0b0100 +const WriteContextJS WriteContext = 0b1000 +const WriteContextAll WriteContext = 0b1111 + +type ContextWriter struct { + wc WriteContext + w io.Writer +} + +func NewContextWriter(w io.Writer, wc WriteContext) ContextWriter { + return ContextWriter{ + wc: wc, + w: w, + } +} + +func (cw ContextWriter) Write(wc WriteContext, s string) (err error) { + if cw.wc.IsSet(wc) { + //TODO: Keep a map of indices to the writecontext, so we can look up in the LSP what type of context (Go, HTML, CSS, JS etc. should be used) + _, err := io.WriteString(cw.w, s) + return err + } + + _, err = io.WriteString(cw.w, whiteSpaceString(s)) + return +} + +// Creates in equivalent length string to the one provided while preserving all white space characters. +func whiteSpaceString(of string) string { + output := []rune(of) + for i, r := range output { + if unicode.IsSpace(r) { + continue + } + output[i] = ' ' + } + return string(output) +} + // Source mapping to map from the source code of the template to the // in-memory representation. type Position struct { @@ -123,24 +170,24 @@ type TemplateFile struct { Nodes []TemplateFileNode } -func (tf TemplateFile) Write(w io.Writer) error { +func (tf TemplateFile) Write(cw ContextWriter) error { for _, n := range tf.Header { - if err := n.Write(w, 0); err != nil { + if err := n.Write(cw, 0); err != nil { return err } } var indent int - if err := tf.Package.Write(w, indent); err != nil { + if err := tf.Package.Write(cw, indent); err != nil { return err } - if _, err := io.WriteString(w, "\n\n"); err != nil { + if err := cw.Write(cw.wc, "\n\n"); err != nil { return err } for i := 0; i < len(tf.Nodes); i++ { - if err := tf.Nodes[i].Write(w, indent); err != nil { + if err := tf.Nodes[i].Write(cw, indent); err != nil { return err } - if _, err := io.WriteString(w, getNodeWhitespace(tf.Nodes, i)); err != nil { + if err := cw.Write(cw.wc, getNodeWhitespace(tf.Nodes, i)); err != nil { return err } } @@ -167,7 +214,7 @@ func endsWithComment(s string) bool { // TemplateFileNode can be a Template, CSS, Script or Go. type TemplateFileNode interface { IsTemplateFileNode() bool - Write(w io.Writer, indent int) error + Write(cw ContextWriter, indent int) error } // TemplateFileGoExpression within a TemplateFile @@ -176,36 +223,37 @@ type TemplateFileGoExpression struct { } func (exp TemplateFileGoExpression) IsTemplateFileNode() bool { return true } -func (exp TemplateFileGoExpression) Write(w io.Writer, indent int) error { +func (exp TemplateFileGoExpression) Write(cw ContextWriter, indent int) error { data, err := format.Source([]byte(exp.Expression.Value)) if err != nil { - return writeIndent(w, indent, exp.Expression.Value) + return writeIndent(cw, WriteContextGo, indent, exp.Expression.Value) + } + if err := cw.Write(WriteContextGo, string(data)); err != nil { + return err } - _, err = w.Write(data) - return err + return nil } -func writeLinesIndented(w io.Writer, level int, s string) (err error) { +func writeLinesIndented(cw ContextWriter, wc WriteContext, level int, s string) (err error) { indent := strings.Repeat("\t", level) lines := strings.Split(s, "\n") indented := strings.Join(lines, "\n"+indent) - if _, err = io.WriteString(w, indent); err != nil { + if err := cw.Write(wc, indent); err != nil { return err } - _, err = io.WriteString(w, indented) - if err != nil { - return + if err := cw.Write(wc, indented); err != nil { + return err } - return + return nil } -func writeIndent(w io.Writer, level int, s ...string) (err error) { +func writeIndent(cw ContextWriter, wc WriteContext, level int, s ...string) (err error) { indent := strings.Repeat("\t", level) - if _, err = io.WriteString(w, indent); err != nil { + if err = cw.Write(cw.wc, indent); err != nil { return err } for _, ss := range s { - _, err = io.WriteString(w, ss) + err = cw.Write(wc, ss) if err != nil { return } @@ -217,8 +265,8 @@ type Package struct { Expression Expression } -func (p Package) Write(w io.Writer, indent int) error { - return writeIndent(w, indent, p.Expression.Value) +func (p Package) Write(cw ContextWriter, indent int) error { + return writeIndent(cw, WriteContextGo, indent, p.Expression.Value) } // Whitespace. @@ -228,7 +276,7 @@ type Whitespace struct { func (ws Whitespace) IsNode() bool { return true } -func (ws Whitespace) Write(w io.Writer, indent int) error { +func (ws Whitespace) Write(cw ContextWriter, indent int) error { if ws.Value == "" || !strings.Contains(ws.Value, "\n") { return nil } @@ -243,8 +291,7 @@ func (ws Whitespace) Write(w io.Writer, indent int) error { // Since any space following another space is ignored, we can collapse to a single rule. // So, the rule is... if there's a newline, it becomes a single space, or it's stripped. // We have to remove the start and end space elsewhere. - _, err := io.WriteString(w, " ") - return err + return cw.Write(cw.wc, " ") } // CSS definition. @@ -261,17 +308,17 @@ type CSSTemplate struct { } func (css CSSTemplate) IsTemplateFileNode() bool { return true } -func (css CSSTemplate) Write(w io.Writer, indent int) error { +func (css CSSTemplate) Write(cw ContextWriter, indent int) error { source := formatFunctionArguments(css.Expression.Value) - if err := writeIndent(w, indent, "css ", string(source), " {\n"); err != nil { + if err := writeIndent(cw, WriteContextGo, indent, "css ", string(source), " {\n"); err != nil { return err } for _, p := range css.Properties { - if err := p.Write(w, indent+1); err != nil { + if err := p.Write(cw, indent+1); err != nil { return err } } - if err := writeIndent(w, indent, "}"); err != nil { + if err := writeIndent(cw, WriteContextGo, indent, "}"); err != nil { return err } return nil @@ -280,7 +327,7 @@ func (css CSSTemplate) Write(w io.Writer, indent int) error { // CSSProperty is a CSS property and value pair. type CSSProperty interface { IsCSSProperty() bool - Write(w io.Writer, indent int) error + Write(cw ContextWriter, indent int) error } // color: #ffffff; @@ -290,8 +337,8 @@ type ConstantCSSProperty struct { } func (c ConstantCSSProperty) IsCSSProperty() bool { return true } -func (c ConstantCSSProperty) Write(w io.Writer, indent int) error { - if err := writeIndent(w, indent, c.String(false)); err != nil { +func (c ConstantCSSProperty) Write(cw ContextWriter, indent int) error { + if err := writeIndent(cw, WriteContextCSS, indent, c.String(false)); err != nil { return err } return nil @@ -320,14 +367,14 @@ type ExpressionCSSProperty struct { } func (c ExpressionCSSProperty) IsCSSProperty() bool { return true } -func (c ExpressionCSSProperty) Write(w io.Writer, indent int) error { - if err := writeIndent(w, indent, c.Name, ": "); err != nil { +func (c ExpressionCSSProperty) Write(cw ContextWriter, indent int) error { + if err := writeIndent(cw, WriteContextCSS, indent, c.Name, ": "); err != nil { return err } - if err := c.Value.Write(w, 0); err != nil { + if err := c.Value.Write(cw, 0); err != nil { return err } - if _, err := w.Write([]byte(";\n")); err != nil { + if err := cw.Write(WriteContextCSS, ";\n"); err != nil { return err } return nil @@ -339,8 +386,8 @@ type DocType struct { } func (dt DocType) IsNode() bool { return true } -func (dt DocType) Write(w io.Writer, indent int) error { - return writeIndent(w, indent, "") +func (dt DocType) Write(cw ContextWriter, indent int) error { + return writeIndent(cw, WriteContextHTML, indent, "") } // HTMLTemplate definition. @@ -357,15 +404,15 @@ type HTMLTemplate struct { func (t HTMLTemplate) IsTemplateFileNode() bool { return true } -func (t HTMLTemplate) Write(w io.Writer, indent int) error { +func (t HTMLTemplate) Write(cw ContextWriter, indent int) error { source := formatFunctionArguments(t.Expression.Value) - if err := writeIndent(w, indent, "templ ", string(source), " {\n"); err != nil { + if err := writeIndent(cw, WriteContextGo, indent, "templ ", string(source), " {\n"); err != nil { return err } - if err := writeNodesIndented(w, indent+1, t.Children); err != nil { + if err := writeNodesIndented(cw, indent+1, t.Children); err != nil { return err } - if err := writeIndent(w, indent, "}"); err != nil { + if err := writeIndent(cw, WriteContextGo, indent, "}"); err != nil { return err } return nil @@ -409,7 +456,7 @@ type Nodes struct { type Node interface { IsNode() bool // Write out the string. - Write(w io.Writer, indent int) error + Write(cw ContextWriter, indent int) error } type CompositeNode interface { @@ -440,8 +487,8 @@ func (t Text) Trailing() TrailingSpace { } func (t Text) IsNode() bool { return true } -func (t Text) Write(w io.Writer, indent int) error { - return writeIndent(w, indent, t.Value) +func (t Text) Write(cw ContextWriter, indent int) error { + return writeIndent(cw, WriteContextHTML, indent, t.Value) } // or
...
@@ -527,8 +574,8 @@ func (e Element) ChildNodes() []Node { return e.Children } func (e Element) IsNode() bool { return true } -func (e Element) Write(w io.Writer, indent int) error { - if err := writeIndent(w, indent, "<", e.Name); err != nil { +func (e Element) Write(cw ContextWriter, indent int) error { + if err := writeIndent(cw, WriteContextHTML, indent, "<", e.Name); err != nil { return err } for i := 0; i < len(e.Attributes); i++ { @@ -536,71 +583,71 @@ func (e Element) Write(w io.Writer, indent int) error { // Only the conditional attributes get indented. var attrIndent int if e.IndentAttrs { - if _, err := w.Write([]byte("\n")); err != nil { + if err := cw.Write(cw.wc, "\n"); err != nil { return err } attrIndent = indent + 1 } else { - if _, err := w.Write([]byte(" ")); err != nil { + if err := cw.Write(cw.wc, " "); err != nil { return err } } - if err := a.Write(w, attrIndent); err != nil { + if err := a.Write(cw, attrIndent); err != nil { return err } } var closeAngleBracketIndent int if e.IndentAttrs { - if _, err := w.Write([]byte("\n")); err != nil { + if err := cw.Write(cw.wc, "\n"); err != nil { return err } closeAngleBracketIndent = indent } if e.hasNonWhitespaceChildren() { if e.IndentChildren { - if err := writeIndent(w, closeAngleBracketIndent, ">\n"); err != nil { + if err := writeIndent(cw, WriteContextHTML, closeAngleBracketIndent, ">\n"); err != nil { return err } - if err := writeNodesIndented(w, indent+1, e.Children); err != nil { + if err := writeNodesIndented(cw, indent+1, e.Children); err != nil { return err } - if err := writeIndent(w, indent, ""); err != nil { + if err := writeIndent(cw, WriteContextHTML, indent, ""); err != nil { return err } return nil } - if err := writeIndent(w, closeAngleBracketIndent, ">"); err != nil { + if err := writeIndent(cw, WriteContextHTML, closeAngleBracketIndent, ">"); err != nil { return err } - if err := writeNodesWithoutIndentation(w, e.Children); err != nil { + if err := writeNodesWithoutIndentation(cw, e.Children); err != nil { return err } - if _, err := w.Write([]byte("")); err != nil { + if err := cw.Write(WriteContextHTML, ""); err != nil { return err } return nil } if e.IsVoidElement() { - if err := writeIndent(w, closeAngleBracketIndent, "/>"); err != nil { + if err := writeIndent(cw, WriteContextHTML, closeAngleBracketIndent, "/>"); err != nil { return err } return nil } - if err := writeIndent(w, closeAngleBracketIndent, ">"); err != nil { + if err := writeIndent(cw, WriteContextHTML, closeAngleBracketIndent, ">"); err != nil { return err } return nil } -func writeNodesWithoutIndentation(w io.Writer, nodes []Node) error { - return writeNodes(w, 0, nodes, false) +func writeNodesWithoutIndentation(cw ContextWriter, nodes []Node) error { + return writeNodes(cw, 0, nodes, false) } -func writeNodesIndented(w io.Writer, level int, nodes []Node) error { - return writeNodes(w, level, nodes, true) +func writeNodesIndented(cw ContextWriter, level int, nodes []Node) error { + return writeNodes(cw, level, nodes, true) } -func writeNodes(w io.Writer, level int, nodes []Node, indent bool) error { +func writeNodes(cw ContextWriter, level int, nodes []Node, indent bool) error { startLevel := level for i := 0; i < len(nodes); i++ { _, isWhitespace := nodes[i].(Whitespace) @@ -609,7 +656,7 @@ func writeNodes(w io.Writer, level int, nodes []Node, indent bool) error { if isWhitespace { continue } - if err := nodes[i].Write(w, level); err != nil { + if err := nodes[i].Write(cw, level); err != nil { return err } @@ -630,7 +677,7 @@ func writeNodes(w io.Writer, level int, nodes []Node, indent bool) error { case SpaceVertical: level = startLevel } - if _, err := w.Write([]byte(trailing)); err != nil { + if err := cw.Write(cw.wc, string(trailing)); err != nil { return err } } @@ -672,30 +719,30 @@ type RawElement struct { } func (e RawElement) IsNode() bool { return true } -func (e RawElement) Write(w io.Writer, indent int) error { +func (e RawElement) Write(cw ContextWriter, indent int) error { // Start. - if err := writeIndent(w, indent, "<", e.Name); err != nil { + if err := writeIndent(cw, WriteContextHTML, indent, "<", e.Name); err != nil { return err } for i := 0; i < len(e.Attributes); i++ { - if _, err := w.Write([]byte(" ")); err != nil { + if err := cw.Write(WriteContextHTML, " "); err != nil { return err } a := e.Attributes[i] // Don't indent the attributes, only the conditional attributes get indented. - if err := a.Write(w, 0); err != nil { + if err := a.Write(cw, 0); err != nil { return err } } - if _, err := w.Write([]byte(">")); err != nil { + if err := cw.Write(WriteContextHTML, ">"); err != nil { return err } // Contents. - if _, err := w.Write([]byte(e.Contents)); err != nil { + if err := cw.Write(cw.wc, e.Contents); err != nil { return err } // Close. - if _, err := w.Write([]byte("")); err != nil { + if err := cw.Write(WriteContextHTML, ""); err != nil { return err } return nil @@ -703,7 +750,7 @@ func (e RawElement) Write(w io.Writer, indent int) error { type Attribute interface { // Write out the string. - Write(w io.Writer, indent int) error + Write(cw ContextWriter, indent int) error } //
@@ -716,8 +763,8 @@ func (bca BoolConstantAttribute) String() string { return bca.Name } -func (bca BoolConstantAttribute) Write(w io.Writer, indent int) error { - return writeIndent(w, indent, bca.String()) +func (bca BoolConstantAttribute) Write(cw ContextWriter, indent int) error { + return writeIndent(cw, WriteContextHTML, indent, bca.Name) } // href="" @@ -736,8 +783,8 @@ func (ca ConstantAttribute) String() string { return ca.Name + `=` + quote + ca.Value + quote } -func (ca ConstantAttribute) Write(w io.Writer, indent int) error { - return writeIndent(w, indent, ca.String()) +func (ca ConstantAttribute) Write(cw ContextWriter, indent int) error { + return writeIndent(cw, WriteContextHTML, indent, ca.String()) } // noshade={ templ.Bool(...) } @@ -751,8 +798,16 @@ func (bea BoolExpressionAttribute) String() string { return bea.Name + `?={ ` + bea.Expression.Value + ` }` } -func (bea BoolExpressionAttribute) Write(w io.Writer, indent int) error { - return writeIndent(w, indent, bea.String()) +func (bea BoolExpressionAttribute) Write(cw ContextWriter, indent int) error { + if err := writeIndent(cw, WriteContextHTML, indent, bea.Name); err != nil { + return err + } + + if cw.wc.IsSet(WriteContextGo) { + return cw.Write(WriteContextGo, "?={ "+bea.Expression.Value+" }") + } else { + return cw.Write(WriteContextHTML, ` =" `+strings.Repeat(" ", len(bea.Expression.Value))+` "`) + } } // href={ ... } @@ -762,12 +817,6 @@ type ExpressionAttribute struct { NameRange Range } -func (ea ExpressionAttribute) String() string { - sb := new(strings.Builder) - _ = ea.Write(sb, 0) - return sb.String() -} - func (ea ExpressionAttribute) formatExpression() (exp []string) { trimmed := strings.TrimSpace(ea.Expression.Value) if !strings.Contains(trimmed, "\n") { @@ -797,21 +846,42 @@ func (ea ExpressionAttribute) formatExpression() (exp []string) { return lines[1 : len(lines)-1] } -func (ea ExpressionAttribute) Write(w io.Writer, indent int) (err error) { +func (ea ExpressionAttribute) Write(cw ContextWriter, indent int) (err error) { lines := ea.formatExpression() + + if err = writeIndent(cw, WriteContextHTML, indent, ea.Name, "="); err != nil { + return err + } + if len(lines) == 1 { - return writeIndent(w, indent, ea.Name, `={ `, lines[0], ` }`) + if cw.wc.IsSet(WriteContextGo) { + return writeIndent(cw, WriteContextGo, 0, `{ `, lines[0], ` }`) + } else { + return writeIndent(cw, WriteContextHTML, 0, `" `, whiteSpaceString(lines[0]), ` "`) + } } - if err = writeIndent(w, indent, ea.Name, "={\n"); err != nil { - return err + if cw.wc.IsSet(WriteContextGo) { + if err := cw.Write(WriteContextGo, "{\n"); err != nil { + return err + } + } else { + if err := cw.Write(cw.wc, `"\n`); err != nil { + return err + } } + for _, line := range lines { - if err = writeIndent(w, indent, line, "\n"); err != nil { + if err = writeIndent(cw, WriteContextGo, indent, line, "\n"); err != nil { return err } } - return writeIndent(w, indent, "}") + + if cw.wc.IsSet(WriteContextGo) { + return writeIndent(cw, WriteContextGo, indent, "}") + } else { + return writeIndent(cw, WriteContextHTML, indent, `"`) + } } //
@@ -823,8 +893,8 @@ func (sa SpreadAttributes) String() string { return `{ ` + sa.Expression.Value + `... }` } -func (sa SpreadAttributes) Write(w io.Writer, indent int) error { - return writeIndent(w, indent, sa.String()) +func (sa SpreadAttributes) Write(cw ContextWriter, indent int) error { + return writeIndent(cw, WriteContextGo, indent, sa.String()) } // ") +func (c HTMLComment) Write(cw ContextWriter, indent int) error { + return writeIndent(cw, WriteContextHTML, indent, "") } // Nodes. @@ -929,9 +993,9 @@ type CallTemplateExpression struct { } func (cte CallTemplateExpression) IsNode() bool { return true } -func (cte CallTemplateExpression) Write(w io.Writer, indent int) error { +func (cte CallTemplateExpression) Write(cw ContextWriter, indent int) error { // Rewrite to new call syntax - return writeIndent(w, indent, `@`, cte.Expression.Value) + return writeIndent(cw, WriteContextGo, indent, `@`, cte.Expression.Value) } // TemplElementExpression can be used to create and render a template using data. @@ -949,24 +1013,24 @@ func (tee TemplElementExpression) ChildNodes() []Node { return tee.Children } func (tee TemplElementExpression) IsNode() bool { return true } -func (tee TemplElementExpression) Write(w io.Writer, indent int) error { +func (tee TemplElementExpression) Write(cw ContextWriter, indent int) error { source, err := format.Source([]byte(tee.Expression.Value)) if err != nil { source = []byte(tee.Expression.Value) } - if err := writeLinesIndented(w, indent, "@"+string(source)); err != nil { + if err := writeLinesIndented(cw, WriteContextGo, indent, "@"+string(source)); err != nil { return err } if len(tee.Children) == 0 { return nil } - if _, err = io.WriteString(w, " {\n"); err != nil { + if err := cw.Write(WriteContextGo, " {\n"); err != nil { return err } - if err := writeNodesIndented(w, indent+1, tee.Children); err != nil { + if err := writeNodesIndented(cw, indent+1, tee.Children); err != nil { return err } - if err := writeIndent(w, indent, "}"); err != nil { + if err := writeIndent(cw, WriteContextGo, indent, "}"); err != nil { return err } return nil @@ -977,8 +1041,8 @@ func (tee TemplElementExpression) Write(w io.Writer, indent int) error { type ChildrenExpression struct{} func (ChildrenExpression) IsNode() bool { return true } -func (ChildrenExpression) Write(w io.Writer, indent int) error { - if err := writeIndent(w, indent, "{ children... }"); err != nil { +func (ChildrenExpression) Write(cw ContextWriter, indent int) error { + if err := writeIndent(cw, WriteContextGo, indent, "{ children... }"); err != nil { return err } return nil @@ -1008,34 +1072,34 @@ func (n IfExpression) ChildNodes() []Node { return nodes } func (n IfExpression) IsNode() bool { return true } -func (n IfExpression) Write(w io.Writer, indent int) error { - if err := writeIndent(w, indent, "if ", n.Expression.Value, " {\n"); err != nil { +func (n IfExpression) Write(cw ContextWriter, indent int) error { + if err := writeIndent(cw, WriteContextGo, indent, "if ", n.Expression.Value, " {\n"); err != nil { return err } indent++ - if err := writeNodesIndented(w, indent, n.Then); err != nil { + if err := writeNodesIndented(cw, indent, n.Then); err != nil { return err } indent-- for _, elseIf := range n.ElseIfs { - if err := writeIndent(w, indent, "} else if ", elseIf.Expression.Value, " {\n"); err != nil { + if err := writeIndent(cw, WriteContextGo, indent, "} else if ", elseIf.Expression.Value, " {\n"); err != nil { return err } indent++ - if err := writeNodesIndented(w, indent, elseIf.Then); err != nil { + if err := writeNodesIndented(cw, indent, elseIf.Then); err != nil { return err } indent-- } if len(n.Else) > 0 { - if err := writeIndent(w, indent, "} else {\n"); err != nil { + if err := writeIndent(cw, WriteContextGo, indent, "} else {\n"); err != nil { return err } - if err := writeNodesIndented(w, indent+1, n.Else); err != nil { + if err := writeNodesIndented(cw, indent+1, n.Else); err != nil { return err } } - if err := writeIndent(w, indent, "}"); err != nil { + if err := writeIndent(cw, WriteContextGo, indent, "}"); err != nil { return err } return nil @@ -1057,22 +1121,22 @@ func (se SwitchExpression) ChildNodes() []Node { return nodes } func (se SwitchExpression) IsNode() bool { return true } -func (se SwitchExpression) Write(w io.Writer, indent int) error { - if err := writeIndent(w, indent, "switch ", se.Expression.Value, " {\n"); err != nil { +func (se SwitchExpression) Write(cw ContextWriter, indent int) error { + if err := writeIndent(cw, WriteContextGo, indent, "switch ", se.Expression.Value, " {\n"); err != nil { return err } indent++ for i := 0; i < len(se.Cases); i++ { c := se.Cases[i] - if err := writeIndent(w, indent, c.Expression.Value, "\n"); err != nil { + if err := writeIndent(cw, WriteContextGo, indent, c.Expression.Value, "\n"); err != nil { return err } - if err := writeNodesIndented(w, indent+1, c.Children); err != nil { + if err := writeNodesIndented(cw, indent+1, c.Children); err != nil { return err } } indent-- - if err := writeIndent(w, indent, "}"); err != nil { + if err := writeIndent(cw, WriteContextGo, indent, "}"); err != nil { return err } return nil @@ -1096,14 +1160,14 @@ func (fe ForExpression) ChildNodes() []Node { return fe.Children } func (fe ForExpression) IsNode() bool { return true } -func (fe ForExpression) Write(w io.Writer, indent int) error { - if err := writeIndent(w, indent, "for ", fe.Expression.Value, " {\n"); err != nil { +func (fe ForExpression) Write(cw ContextWriter, indent int) error { + if err := writeIndent(cw, WriteContextGo, indent, "for ", fe.Expression.Value, " {\n"); err != nil { return err } - if err := writeNodesIndented(w, indent+1, fe.Children); err != nil { + if err := writeNodesIndented(cw, indent+1, fe.Children); err != nil { return err } - if err := writeIndent(w, indent, "}"); err != nil { + if err := writeIndent(cw, WriteContextGo, indent, "}"); err != nil { return err } return nil @@ -1123,11 +1187,18 @@ func (se StringExpression) Trailing() TrailingSpace { func (se StringExpression) IsNode() bool { return true } func (se StringExpression) IsStyleDeclarationValue() bool { return true } -func (se StringExpression) Write(w io.Writer, indent int) error { +func (se StringExpression) Write(cw ContextWriter, indent int) error { if isWhitespace(se.Expression.Value) { se.Expression.Value = "" } - return writeIndent(w, indent, `{ `, se.Expression.Value, ` }`) + + if cw.wc.IsSet(WriteContextGo) { + return writeIndent(cw, WriteContextGo, indent, `{ `, se.Expression.Value, ` }`) + } else if cw.wc.IsSet(WriteContextCSS) { + return writeIndent(cw, WriteContextCSS, indent, `' `, whiteSpaceString(se.Expression.Value), ` '`) + } else { + return writeIndent(cw, WriteContextHTML, indent, `" `, whiteSpaceString(se.Expression.Value), ` "`) + } } // ScriptTemplate is a script block. @@ -1138,15 +1209,15 @@ type ScriptTemplate struct { } func (s ScriptTemplate) IsTemplateFileNode() bool { return true } -func (s ScriptTemplate) Write(w io.Writer, indent int) error { +func (s ScriptTemplate) Write(cw ContextWriter, indent int) error { source := formatFunctionArguments(s.Name.Value + "(" + s.Parameters.Value + ")") - if err := writeIndent(w, indent, "script ", string(source), " {\n"); err != nil { + if err := writeIndent(cw, WriteContextGo, indent, "script ", string(source), " {\n"); err != nil { return err } - if _, err := io.WriteString(w, s.Value); err != nil { + if err := cw.Write(WriteContextJS, s.Value); err != nil { return err } - if err := writeIndent(w, indent, "}"); err != nil { + if err := writeIndent(cw, WriteContextGo, indent, "}"); err != nil { return err } return nil diff --git a/parser/v2/types_test.go b/parser/v2/types_test.go index 312aa007a..fe47baa85 100644 --- a/parser/v2/types_test.go +++ b/parser/v2/types_test.go @@ -833,7 +833,8 @@ templ test() { t.Fatalf("failed to parse template: %v", err) } w := new(strings.Builder) - err = template.Write(w) + cw := NewContextWriter(w, WriteContextAll) + err = template.Write(cw) if err != nil { t.Fatalf("failed to write template: %v", err) }